From 471e21cf5b60a53c9527466845e4f4dafacae000 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sun, 27 Aug 2023 21:43:54 +0200
Subject: [PATCH 01/56] fix #141: Check digits when converting string to long

---
 include/scratchcpp/value.h | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/include/scratchcpp/value.h b/include/scratchcpp/value.h
index c50b8de4..5f230b7c 100644
--- a/include/scratchcpp/value.h
+++ b/include/scratchcpp/value.h
@@ -660,6 +660,14 @@ class LIBSCRATCHCPP_EXPORT Value : public ValueVariant
                 return 0;
             }
 
+            static const std::string digits = "0123456789+-";
+
+            for (char c : s) {
+                if (digits.find(c) == std::string::npos) {
+                    return 0;
+                }
+            }
+
             try {
                 if (ok)
                     *ok = true;

From b84ea52eb71de521ed9305fc602fd10a37bfb7ed Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sun, 27 Aug 2023 21:44:13 +0200
Subject: [PATCH 02/56] Add test for #141

---
 test/scratch_classes/value_test.cpp | 24 ++++++++++++++++++++++++
 1 file changed, 24 insertions(+)

diff --git a/test/scratch_classes/value_test.cpp b/test/scratch_classes/value_test.cpp
index 2a5d14c3..bab6d681 100644
--- a/test/scratch_classes/value_test.cpp
+++ b/test/scratch_classes/value_test.cpp
@@ -123,6 +123,30 @@ TEST(ValueTest, StdStringConstructor)
         ASSERT_TRUE(v.isString());
     }
 
+    {
+        Value v(std::string("532"));
+        ASSERT_EQ(v.toString(), "532");
+        ASSERT_EQ(v.type(), Value::Type::Number);
+        ASSERT_FALSE(v.isInfinity());
+        ASSERT_FALSE(v.isNegativeInfinity());
+        ASSERT_FALSE(v.isNaN());
+        ASSERT_TRUE(v.isNumber());
+        ASSERT_FALSE(v.isBool());
+        ASSERT_FALSE(v.isString());
+    }
+
+    {
+        Value v(std::string("1 2 3"));
+        ASSERT_EQ(v.toString(), "1 2 3");
+        ASSERT_EQ(v.type(), Value::Type::String);
+        ASSERT_FALSE(v.isInfinity());
+        ASSERT_FALSE(v.isNegativeInfinity());
+        ASSERT_FALSE(v.isNaN());
+        ASSERT_FALSE(v.isNumber());
+        ASSERT_FALSE(v.isBool());
+        ASSERT_TRUE(v.isString());
+    }
+
     {
         Value v(std::string("Infinity"));
         ASSERT_EQ(v.toString(), "Infinity");

From 1ac93b897273fd7a3c274bf450f33165003aa9c7 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Mon, 28 Aug 2023 12:27:40 +0200
Subject: [PATCH 03/56] Refactor Value using union

---
 include/scratchcpp/value.h          | 338 +++++++++++++++++-----------
 src/scratch/inputvalue.cpp          |   6 +-
 test/scratch_classes/value_test.cpp |  22 +-
 3 files changed, 219 insertions(+), 147 deletions(-)

diff --git a/include/scratchcpp/value.h b/include/scratchcpp/value.h
index 5f230b7c..22e48559 100644
--- a/include/scratchcpp/value.h
+++ b/include/scratchcpp/value.h
@@ -14,10 +14,8 @@
 namespace libscratchcpp
 {
 
-using ValueVariant = std::variant<long, double, bool, std::string>;
-
 /*! \brief The Value class represents a Scratch value. */
-class LIBSCRATCHCPP_EXPORT Value : public ValueVariant
+class LIBSCRATCHCPP_EXPORT Value
 {
     public:
         enum class SpecialValue
@@ -29,9 +27,10 @@ class LIBSCRATCHCPP_EXPORT Value : public ValueVariant
 
         enum class Type
         {
-            Number = 0,
-            Bool = 1,
-            String = 2,
+            Integer = 0,
+            Double = 1,
+            Bool = 2,
+            String = 3,
             Infinity = -1,
             NegativeInfinity = -2,
             NaN = -3
@@ -39,51 +38,51 @@ class LIBSCRATCHCPP_EXPORT Value : public ValueVariant
 
         /*! Constructs a number Value. */
         Value(float numberValue) :
-            ValueVariant(numberValue),
-            m_type(Type::Number)
+            m_type(Type::Double)
         {
+            m_doubleValue = numberValue;
         }
 
         /*! Constructs a number Value. */
         Value(double numberValue) :
-            ValueVariant(numberValue),
-            m_type(Type::Number)
+            m_type(Type::Double)
         {
+            m_doubleValue = numberValue;
         }
 
         /*! Constructs a number Value. */
         Value(int numberValue = 0) :
-            ValueVariant(numberValue),
-            m_type(Type::Number)
+            m_type(Type::Integer)
         {
+            m_intValue = numberValue;
         }
 
         /*! Constructs a number Value. */
         Value(size_t numberValue) :
-            ValueVariant(static_cast<long>(numberValue)),
-            m_type(Type::Number)
+            m_type(Type::Integer)
         {
+            m_intValue = numberValue;
         }
 
         /*! Constructs a number Value. */
         Value(long numberValue) :
-            ValueVariant(numberValue),
-            m_type(Type::Number)
+            m_type(Type::Integer)
         {
+            m_intValue = numberValue;
         }
 
         /*! Constructs a boolean Value. */
         Value(bool boolValue) :
-            ValueVariant(boolValue),
             m_type(Type::Bool)
         {
+            m_boolValue = boolValue;
         }
 
         /*! Constructs a string Value. */
         Value(const std::string &stringValue) :
-            ValueVariant(stringValue),
             m_type(Type::String)
         {
+            new (&m_stringValue) std::string(stringValue);
             initString(stringValue);
         }
 
@@ -94,8 +93,7 @@ class LIBSCRATCHCPP_EXPORT Value : public ValueVariant
         }
 
         /*! Constructs a special Value. */
-        Value(SpecialValue specialValue) :
-            ValueVariant(0)
+        Value(SpecialValue specialValue)
         {
             if (specialValue == SpecialValue::Infinity)
                 m_type = Type::Infinity;
@@ -103,8 +101,38 @@ class LIBSCRATCHCPP_EXPORT Value : public ValueVariant
                 m_type = Type::NegativeInfinity;
             else if (specialValue == SpecialValue::NaN)
                 m_type = Type::NaN;
-            else
-                m_type = Type::Number;
+            else {
+                m_type = Type::Integer;
+                m_intValue = 0;
+            }
+        }
+
+        Value(const Value &v)
+        {
+            m_type = v.m_type;
+
+            switch (m_type) {
+                case Type::Integer:
+                    m_intValue = v.m_intValue;
+                    break;
+                case Type::Double:
+                    m_doubleValue = v.m_doubleValue;
+                    break;
+                case Type::Bool:
+                    m_boolValue = v.m_boolValue;
+                    break;
+                case Type::String:
+                    new (&m_stringValue) std::string(v.m_stringValue);
+                    break;
+                default:
+                    break;
+            }
+        }
+
+        ~Value()
+        {
+            if (m_type == Type::String)
+                m_stringValue.~basic_string();
         }
 
         /*! Returns the type of the value. */
@@ -120,7 +148,7 @@ class LIBSCRATCHCPP_EXPORT Value : public ValueVariant
         bool isNaN() const { return m_type == Type::NaN; }
 
         /*! Returns true if the value is a number. */
-        bool isNumber() const { return m_type == Type::Number; }
+        bool isNumber() const { return m_type == Type::Integer || m_type == Type::Double; }
 
         /*! Returns true if the value is a boolean. */
         bool isBool() const { return m_type == Type::Bool; }
@@ -135,23 +163,27 @@ class LIBSCRATCHCPP_EXPORT Value : public ValueVariant
         long toLong() const
         {
             if (static_cast<int>(m_type) < 0) {
-                if (m_type == Type::Infinity)
-                    return std::numeric_limits<long>::infinity();
-                else if (m_type == Type::NegativeInfinity)
-                    return -std::numeric_limits<long>::infinity();
-                else
-                    return 0;
+                switch (m_type) {
+                    case Type::Infinity:
+                        return std::numeric_limits<long>::infinity();
+                    case Type::NegativeInfinity:
+                        return -std::numeric_limits<long>::infinity();
+                    default:
+                        return 0;
+                }
             } else {
-                if (auto p = std::get_if<long>(this))
-                    return *p;
-                else if (auto p = std::get_if<double>(this))
-                    return *p;
-                else if (auto p = std::get_if<bool>(this))
-                    return *p;
-                else if (auto p = std::get_if<std::string>(this))
-                    return stringToLong(*p);
-                else
-                    return 0;
+                switch (m_type) {
+                    case Type::Integer:
+                        return m_intValue;
+                    case Type::Double:
+                        return m_doubleValue;
+                    case Type::Bool:
+                        return m_boolValue;
+                    case Type::String:
+                        return stringToLong(m_stringValue);
+                    default:
+                        return 0;
+                }
             }
         }
 
@@ -159,23 +191,27 @@ class LIBSCRATCHCPP_EXPORT Value : public ValueVariant
         double toDouble() const
         {
             if (static_cast<int>(m_type) < 0) {
-                if (m_type == Type::Infinity)
-                    return std::numeric_limits<double>::infinity();
-                if (m_type == Type::NegativeInfinity)
-                    return -std::numeric_limits<double>::infinity();
-                else
-                    return 0;
+                switch (m_type) {
+                    case Type::Infinity:
+                        return std::numeric_limits<double>::infinity();
+                    case Type::NegativeInfinity:
+                        return -std::numeric_limits<double>::infinity();
+                    default:
+                        return 0;
+                }
             } else {
-                if (std::holds_alternative<double>(*this))
-                    return std::get<double>(*this);
-                else if (std::holds_alternative<long>(*this))
-                    return std::get<long>(*this);
-                else if (std::holds_alternative<bool>(*this))
-                    return std::get<bool>(*this);
-                else if (std::holds_alternative<std::string>(*this))
-                    return stringToDouble(std::get<std::string>(*this));
-                else
-                    return 0;
+                switch (m_type) {
+                    case Type::Double:
+                        return m_doubleValue;
+                    case Type::Integer:
+                        return m_intValue;
+                    case Type::Bool:
+                        return m_boolValue;
+                    case Type::String:
+                        return stringToDouble(m_stringValue);
+                    default:
+                        return 0;
+                }
             }
         };
 
@@ -185,17 +221,20 @@ class LIBSCRATCHCPP_EXPORT Value : public ValueVariant
             if (static_cast<int>(m_type) < 0) {
                 return false;
             } else {
-                if (std::holds_alternative<bool>(*this))
-                    return std::get<bool>(*this);
-                else if (std::holds_alternative<long>(*this))
-                    return std::get<long>(*this) == 1 ? true : false;
-                else if (std::holds_alternative<double>(*this))
-                    return std::get<double>(*this) == 1 ? true : false;
-                else if (std::holds_alternative<std::string>(*this)) {
-                    const std::string &str = std::get<std::string>(*this);
-                    return (stringsEqual(str, "true") || str == "1");
-                } else
-                    return false;
+                switch (m_type) {
+                    case Type::Bool:
+                        return m_boolValue;
+                    case Type::Integer:
+                        return m_intValue == 1 ? true : false;
+                    case Type::Double:
+                        return m_doubleValue == 1 ? true : false;
+                    case Type::String: {
+                        const std::string &str = m_stringValue;
+                        return (stringsEqual(str, "true") || str == "1");
+                    }
+                    default:
+                        return false;
+                }
             }
         };
 
@@ -203,28 +242,33 @@ class LIBSCRATCHCPP_EXPORT Value : public ValueVariant
         std::string toString() const
         {
             if (static_cast<int>(m_type) < 0) {
-                if (m_type == Type::Infinity)
-                    return "Infinity";
-                else if (m_type == Type::NegativeInfinity)
-                    return "-Infinity";
-                else
-                    return "NaN";
+                switch (m_type) {
+                    case Type::Infinity:
+                        return "Infinity";
+                    case Type::NegativeInfinity:
+                        return "-Infinity";
+                    default:
+                        return "NaN";
+                }
             } else {
-                if (std::holds_alternative<std::string>(*this))
-                    return std::get<std::string>(*this);
-                else if (std::holds_alternative<long>(*this))
-                    return std::to_string(std::get<long>(*this));
-                else if (std::holds_alternative<double>(*this)) {
-                    std::string s = std::to_string(std::get<double>(*this));
-                    s.erase(s.find_last_not_of('0') + 1, std::string::npos);
-                    if (s.back() == '.') {
-                        s.pop_back();
+                switch (m_type) {
+                    case Type::String:
+                        return m_stringValue;
+                    case Type::Integer:
+                        return std::to_string(m_intValue);
+                    case Type::Double: {
+                        std::string s = std::to_string(m_doubleValue);
+                        s.erase(s.find_last_not_of('0') + 1, std::string::npos);
+                        if (s.back() == '.') {
+                            s.pop_back();
+                        }
+                        return s;
                     }
-                    return s;
-                } else if (std::holds_alternative<bool>(*this))
-                    return std::get<bool>(*this) ? "true" : "false";
-                else
-                    return "";
+                    case Type::Bool:
+                        return m_boolValue ? "true" : "false";
+                    default:
+                        return "";
+                }
             }
         };
 
@@ -243,10 +287,9 @@ class LIBSCRATCHCPP_EXPORT Value : public ValueVariant
                     m_type = Type::NegativeInfinity;
                 return;
             }
-            auto p1 = std::get_if<long>(this);
-            auto p2 = std::get_if<long>(&v);
-            if (p1 && p2)
-                *this = *p1 + *p2;
+
+            if (m_type == Type::Integer && v.m_type == Type::Integer)
+                m_intValue += v.m_intValue;
             else
                 *this = toDouble() + v.toDouble();
         }
@@ -263,10 +306,9 @@ class LIBSCRATCHCPP_EXPORT Value : public ValueVariant
                     m_type = Type::NegativeInfinity;
                 return;
             }
-            auto p1 = std::get_if<long>(this);
-            auto p2 = std::get_if<long>(&v);
-            if (p1 && p2)
-                *this = *p1 - *p2;
+
+            if (m_type == Type::Integer && v.m_type == Type::Integer)
+                m_intValue -= v.m_intValue;
             else
                 *this = toDouble() - v.toDouble();
         }
@@ -288,10 +330,9 @@ class LIBSCRATCHCPP_EXPORT Value : public ValueVariant
                     return;
                 }
             }
-            auto p1 = std::get_if<long>(this);
-            auto p2 = std::get_if<long>(&v);
-            if (p1 && p2)
-                *this = *p1 * *p2;
+
+            if (m_type == Type::Integer && v.m_type == Type::Integer)
+                m_intValue *= v.m_intValue;
             else
                 *this = toDouble() * v.toDouble();
         }
@@ -336,43 +377,43 @@ class LIBSCRATCHCPP_EXPORT Value : public ValueVariant
 
         const Value &operator=(float v)
         {
-            m_type = Type::Number;
-            ValueVariant::operator=(v);
+            m_type = Type::Double;
+            m_doubleValue = v;
             return *this;
         }
 
         const Value &operator=(double v)
         {
-            m_type = Type::Number;
-            ValueVariant::operator=(v);
+            m_type = Type::Double;
+            m_doubleValue = v;
             return *this;
         }
 
         const Value &operator=(int v)
         {
-            m_type = Type::Number;
-            ValueVariant::operator=(v);
+            m_type = Type::Integer;
+            m_intValue = v;
             return *this;
         }
 
         const Value &operator=(long v)
         {
-            m_type = Type::Number;
-            ValueVariant::operator=(v);
+            m_type = Type::Integer;
+            m_intValue = v;
             return *this;
         }
 
         const Value &operator=(bool v)
         {
             m_type = Type::Bool;
-            ValueVariant::operator=(v);
+            m_boolValue = v;
             return *this;
         }
 
         const Value &operator=(const std::string &v)
         {
             m_type = Type::String;
-            ValueVariant::operator=(v);
+            new (&m_stringValue) std::string(v);
             initString(v);
             return *this;
         }
@@ -380,12 +421,44 @@ class LIBSCRATCHCPP_EXPORT Value : public ValueVariant
         const Value &operator=(const char *v)
         {
             m_type = Type::String;
-            ValueVariant::operator=(std::string(v));
+            new (&m_stringValue) std::string(v);
             initString(v);
             return *this;
         }
 
+        const Value &operator=(const Value &v)
+        {
+            m_type = v.m_type;
+
+            switch (m_type) {
+                case Type::Integer:
+                    m_intValue = v.m_intValue;
+                    break;
+                case Type::Double:
+                    m_doubleValue = v.m_doubleValue;
+                    break;
+                case Type::Bool:
+                    m_boolValue = v.m_boolValue;
+                    break;
+                case Type::String:
+                    new (&m_stringValue) std::string(v.m_stringValue);
+                    break;
+                default:
+                    break;
+            }
+
+            return *this;
+        }
+
     private:
+        union
+        {
+                long m_intValue;
+                double m_doubleValue;
+                bool m_boolValue;
+                std::string m_stringValue;
+        };
+
         void initString(const std::string &str)
         {
             if (str.empty())
@@ -411,11 +484,13 @@ class LIBSCRATCHCPP_EXPORT Value : public ValueVariant
             } else
                 d = stringToDouble(str, &ok);
             if (ok) {
-                if (isLong)
+                if (isLong) {
                     *this = l;
-                else
+                    m_type = Type::Integer;
+                } else {
                     *this = d;
-                m_type = Type::Number;
+                    m_type = Type::Double;
+                }
             }
         }
 
@@ -428,7 +503,9 @@ class LIBSCRATCHCPP_EXPORT Value : public ValueVariant
             if (v1.m_type == v2.m_type) {
                 auto type = v1.m_type;
                 switch (type) {
-                    case Type::Number:
+                    case Type::Integer:
+                        return v1.toLong() == v2.toLong();
+                    case Type::Double:
                         return v1.toDouble() == v2.toDouble();
                     case Type::Bool:
                         return v1.toBool() == v2.toBool();
@@ -467,10 +544,9 @@ class LIBSCRATCHCPP_EXPORT Value : public ValueVariant
                 else if (v2.m_type == Type::NegativeInfinity)
                     return true;
             }
-            auto p1 = std::get_if<long>(&v1);
-            auto p2 = std::get_if<long>(&v2);
-            if (p1 && p2)
-                return *p1 > *p2;
+
+            if (v1.m_type == Type::Integer && v2.m_type == Type::Integer)
+                return v1.m_intValue > v2.m_intValue;
             else
                 return v1.toDouble() > v2.toDouble();
         }
@@ -487,10 +563,9 @@ class LIBSCRATCHCPP_EXPORT Value : public ValueVariant
                 else if (v2.m_type == Type::NegativeInfinity)
                     return false;
             }
-            auto p1 = std::get_if<long>(&v1);
-            auto p2 = std::get_if<long>(&v2);
-            if (p1 && p2)
-                return *p1 < *p2;
+
+            if (v1.m_type == Type::Integer && v2.m_type == Type::Integer)
+                return v1.m_intValue < v2.m_intValue;
             else
                 return v1.toDouble() < v2.toDouble();
         }
@@ -509,10 +584,9 @@ class LIBSCRATCHCPP_EXPORT Value : public ValueVariant
                 else if (v1.m_type == Type::NegativeInfinity || v2.m_type == Type::NegativeInfinity)
                     return Value(SpecialValue::NegativeInfinity);
             }
-            auto p1 = std::get_if<long>(&v1);
-            auto p2 = std::get_if<long>(&v2);
-            if (p1 && p2)
-                return *p1 + *p2;
+
+            if (v1.m_type == Type::Integer && v2.m_type == Type::Integer)
+                return v1.m_intValue + v2.m_intValue;
             else
                 return v1.toDouble() + v2.toDouble();
         }
@@ -527,10 +601,9 @@ class LIBSCRATCHCPP_EXPORT Value : public ValueVariant
                 else if (v1.m_type == Type::NegativeInfinity || v2.m_type == Type::Infinity)
                     return Value(SpecialValue::NegativeInfinity);
             }
-            auto p1 = std::get_if<long>(&v1);
-            auto p2 = std::get_if<long>(&v2);
-            if (p1 && p2)
-                return *p1 - *p2;
+
+            if (v1.m_type == Type::Integer && v2.m_type == Type::Integer)
+                return v1.m_intValue - v2.m_intValue;
             else
                 return v1.toDouble() - v2.toDouble();
         }
@@ -550,10 +623,9 @@ class LIBSCRATCHCPP_EXPORT Value : public ValueVariant
                         return Value(SpecialValue::NaN);
                 }
             }
-            auto p1 = std::get_if<long>(&v1);
-            auto p2 = std::get_if<long>(&v2);
-            if (p1 && p2)
-                return *p1 * *p2;
+
+            if (v1.m_type == Type::Integer && v2.m_type == Type::Integer)
+                return v1.m_intValue * v2.m_intValue;
             else
                 return v1.toDouble() * v2.toDouble();
         }
diff --git a/src/scratch/inputvalue.cpp b/src/scratch/inputvalue.cpp
index 4eb02004..f16f74b3 100644
--- a/src/scratch/inputvalue.cpp
+++ b/src/scratch/inputvalue.cpp
@@ -14,9 +14,9 @@
 using namespace libscratchcpp;
 
 static const std::map<Value::Type, InputValue::Type> VALUE_TYPE_MAP = {
-    { Value::Type::Number, InputValue::Type::Number },           { Value::Type::Bool, InputValue::Type::String },
-    { Value::Type::String, InputValue::Type::String },           { Value::Type::Infinity, InputValue::Type::String },
-    { Value::Type::NegativeInfinity, InputValue::Type::String }, { Value::Type::NaN, InputValue::Type::String }
+    { Value::Type::Integer, InputValue::Type::Number }, { Value::Type::Double, InputValue::Type::Number },   { Value::Type::Bool, InputValue::Type::String },
+    { Value::Type::String, InputValue::Type::String },  { Value::Type::Infinity, InputValue::Type::String }, { Value::Type::NegativeInfinity, InputValue::Type::String },
+    { Value::Type::NaN, InputValue::Type::String }
 };
 
 /*! Constructs InputValue with the given type. */
diff --git a/test/scratch_classes/value_test.cpp b/test/scratch_classes/value_test.cpp
index bab6d681..aea35f36 100644
--- a/test/scratch_classes/value_test.cpp
+++ b/test/scratch_classes/value_test.cpp
@@ -8,7 +8,7 @@ TEST(ValueTest, DefaultConstructor)
 {
     Value v;
     ASSERT_EQ(v.toInt(), 0);
-    ASSERT_EQ(v.type(), Value::Type::Number);
+    ASSERT_EQ(v.type(), Value::Type::Integer);
     ASSERT_FALSE(v.isInfinity());
     ASSERT_FALSE(v.isNegativeInfinity());
     ASSERT_FALSE(v.isNaN());
@@ -21,7 +21,7 @@ TEST(ValueTest, FloatConstructor)
 {
     Value v(3.14f);
     ASSERT_EQ(v.toDouble(), 3.14f);
-    ASSERT_EQ(v.type(), Value::Type::Number);
+    ASSERT_EQ(v.type(), Value::Type::Double);
     ASSERT_FALSE(v.isInfinity());
     ASSERT_FALSE(v.isNegativeInfinity());
     ASSERT_FALSE(v.isNaN());
@@ -34,7 +34,7 @@ TEST(ValueTest, DoubleConstructor)
 {
     Value v(static_cast<double>(3.14));
     ASSERT_EQ(v.toDouble(), 3.14);
-    ASSERT_EQ(v.type(), Value::Type::Number);
+    ASSERT_EQ(v.type(), Value::Type::Double);
     ASSERT_FALSE(v.isInfinity());
     ASSERT_FALSE(v.isNegativeInfinity());
     ASSERT_FALSE(v.isNaN());
@@ -47,7 +47,7 @@ TEST(ValueTest, IntConstructor)
 {
     Value v(static_cast<int>(55));
     ASSERT_EQ(v.toInt(), 55);
-    ASSERT_EQ(v.type(), Value::Type::Number);
+    ASSERT_EQ(v.type(), Value::Type::Integer);
     ASSERT_FALSE(v.isInfinity());
     ASSERT_FALSE(v.isNegativeInfinity());
     ASSERT_FALSE(v.isNaN());
@@ -60,7 +60,7 @@ TEST(ValueTest, SizeTConstructor)
 {
     Value v(static_cast<size_t>(100));
     ASSERT_EQ(v.toLong(), 100);
-    ASSERT_EQ(v.type(), Value::Type::Number);
+    ASSERT_EQ(v.type(), Value::Type::Integer);
     ASSERT_FALSE(v.isInfinity());
     ASSERT_FALSE(v.isNegativeInfinity());
     ASSERT_FALSE(v.isNaN());
@@ -73,7 +73,7 @@ TEST(ValueTest, LongConstructor)
 {
     Value v(999999999999999999L);
     ASSERT_EQ(v.toLong(), 999999999999999999L);
-    ASSERT_EQ(v.type(), Value::Type::Number);
+    ASSERT_EQ(v.type(), Value::Type::Integer);
     ASSERT_FALSE(v.isInfinity());
     ASSERT_FALSE(v.isNegativeInfinity());
     ASSERT_FALSE(v.isNaN());
@@ -126,7 +126,7 @@ TEST(ValueTest, StdStringConstructor)
     {
         Value v(std::string("532"));
         ASSERT_EQ(v.toString(), "532");
-        ASSERT_EQ(v.type(), Value::Type::Number);
+        ASSERT_EQ(v.type(), Value::Type::Integer);
         ASSERT_FALSE(v.isInfinity());
         ASSERT_FALSE(v.isNegativeInfinity());
         ASSERT_FALSE(v.isNaN());
@@ -290,7 +290,7 @@ TEST(ValueTest, FloatAssignment)
     Value v;
     v = 3.14f;
     ASSERT_EQ(v.toDouble(), 3.14f);
-    ASSERT_EQ(v.type(), Value::Type::Number);
+    ASSERT_EQ(v.type(), Value::Type::Double);
     ASSERT_FALSE(v.isInfinity());
     ASSERT_FALSE(v.isNegativeInfinity());
     ASSERT_FALSE(v.isNaN());
@@ -304,7 +304,7 @@ TEST(ValueTest, DoubleAssignment)
     Value v;
     v = static_cast<double>(3.14);
     ASSERT_EQ(v.toDouble(), 3.14);
-    ASSERT_EQ(v.type(), Value::Type::Number);
+    ASSERT_EQ(v.type(), Value::Type::Double);
     ASSERT_FALSE(v.isInfinity());
     ASSERT_FALSE(v.isNegativeInfinity());
     ASSERT_FALSE(v.isNaN());
@@ -318,7 +318,7 @@ TEST(ValueTest, IntAssignment)
     Value v;
     v = static_cast<int>(55);
     ASSERT_EQ(v.toInt(), 55);
-    ASSERT_EQ(v.type(), Value::Type::Number);
+    ASSERT_EQ(v.type(), Value::Type::Integer);
     ASSERT_FALSE(v.isInfinity());
     ASSERT_FALSE(v.isNegativeInfinity());
     ASSERT_FALSE(v.isNaN());
@@ -332,7 +332,7 @@ TEST(ValueTest, LongAssignment)
     Value v;
     v = 999999999999999999L;
     ASSERT_EQ(v.toLong(), 999999999999999999L);
-    ASSERT_EQ(v.type(), Value::Type::Number);
+    ASSERT_EQ(v.type(), Value::Type::Integer);
     ASSERT_FALSE(v.isInfinity());
     ASSERT_FALSE(v.isNegativeInfinity());
     ASSERT_FALSE(v.isNaN());

From 55fd54fb831bd3d9c35552eee7874d02b137d384 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Mon, 28 Aug 2023 12:58:14 +0200
Subject: [PATCH 04/56] Add proper tests for copy constructor/assignment of
 Value

---
 test/scratch_classes/value_test.cpp | 204 +++++++++++++++++++++++++---
 1 file changed, 183 insertions(+), 21 deletions(-)

diff --git a/test/scratch_classes/value_test.cpp b/test/scratch_classes/value_test.cpp
index aea35f36..ec5ece7b 100644
--- a/test/scratch_classes/value_test.cpp
+++ b/test/scratch_classes/value_test.cpp
@@ -273,16 +273,94 @@ TEST(ValueTest, NaNConstructor)
 
 TEST(ValueTest, CopyConstructor)
 {
-    Value v1(50);
-    Value v2(v1);
-    ASSERT_EQ(v2.toInt(), 50);
-    ASSERT_EQ(v1.type(), v2.type());
-    ASSERT_FALSE(v2.isInfinity());
-    ASSERT_FALSE(v2.isNegativeInfinity());
-    ASSERT_FALSE(v2.isNaN());
-    ASSERT_TRUE(v2.isNumber());
-    ASSERT_FALSE(v2.isBool());
-    ASSERT_FALSE(v2.isString());
+    {
+        Value v1(50);
+        Value v2(v1);
+        ASSERT_EQ(v1.toInt(), v2.toInt());
+        ASSERT_EQ(v1.type(), v2.type());
+        ASSERT_FALSE(v2.isInfinity());
+        ASSERT_FALSE(v2.isNegativeInfinity());
+        ASSERT_FALSE(v2.isNaN());
+        ASSERT_TRUE(v2.isNumber());
+        ASSERT_FALSE(v2.isBool());
+        ASSERT_FALSE(v2.isString());
+    }
+
+    {
+        Value v1(5.5);
+        Value v2(v1);
+        ASSERT_EQ(v1.toDouble(), v2.toDouble());
+        ASSERT_EQ(v1.type(), v2.type());
+        ASSERT_FALSE(v2.isInfinity());
+        ASSERT_FALSE(v2.isNegativeInfinity());
+        ASSERT_FALSE(v2.isNaN());
+        ASSERT_TRUE(v2.isNumber());
+        ASSERT_FALSE(v2.isBool());
+        ASSERT_FALSE(v2.isString());
+    }
+
+    {
+        Value v1(true);
+        Value v2(v1);
+        ASSERT_EQ(v1.toBool(), v2.toBool());
+        ASSERT_EQ(v1.type(), v2.type());
+        ASSERT_FALSE(v2.isInfinity());
+        ASSERT_FALSE(v2.isNegativeInfinity());
+        ASSERT_FALSE(v2.isNaN());
+        ASSERT_FALSE(v2.isNumber());
+        ASSERT_TRUE(v2.isBool());
+        ASSERT_FALSE(v2.isString());
+    }
+
+    {
+        Value v1("test");
+        Value v2(v1);
+        ASSERT_EQ(v1.toString(), v2.toString());
+        ASSERT_EQ(v1.type(), v2.type());
+        ASSERT_FALSE(v2.isInfinity());
+        ASSERT_FALSE(v2.isNegativeInfinity());
+        ASSERT_FALSE(v2.isNaN());
+        ASSERT_FALSE(v2.isNumber());
+        ASSERT_FALSE(v2.isBool());
+        ASSERT_TRUE(v2.isString());
+    }
+
+    {
+        Value v1(Value::SpecialValue::NaN);
+        Value v2(v1);
+        ASSERT_EQ(v2.toDouble(), 0);
+        ASSERT_EQ(v1.type(), v2.type());
+        ASSERT_FALSE(v2.isInfinity());
+        ASSERT_FALSE(v2.isNegativeInfinity());
+        ASSERT_TRUE(v2.isNaN());
+        ASSERT_FALSE(v2.isNumber());
+        ASSERT_FALSE(v2.isBool());
+        ASSERT_FALSE(v2.isString());
+    }
+
+    {
+        Value v1(Value::SpecialValue::Infinity);
+        Value v2(v1);
+        ASSERT_EQ(v1.type(), v2.type());
+        ASSERT_TRUE(v2.isInfinity());
+        ASSERT_FALSE(v2.isNegativeInfinity());
+        ASSERT_FALSE(v2.isNaN());
+        ASSERT_FALSE(v2.isNumber());
+        ASSERT_FALSE(v2.isBool());
+        ASSERT_FALSE(v2.isString());
+    }
+
+    {
+        Value v1(Value::SpecialValue::NegativeInfinity);
+        Value v2(v1);
+        ASSERT_EQ(v1.type(), v2.type());
+        ASSERT_FALSE(v2.isInfinity());
+        ASSERT_TRUE(v2.isNegativeInfinity());
+        ASSERT_FALSE(v2.isNaN());
+        ASSERT_FALSE(v2.isNumber());
+        ASSERT_FALSE(v2.isBool());
+        ASSERT_FALSE(v2.isString());
+    }
 }
 
 TEST(ValueTest, FloatAssignment)
@@ -521,17 +599,101 @@ TEST(ValueTest, NaNAssignment)
 
 TEST(ValueTest, CopyAssignment)
 {
-    Value v1(50);
-    Value v2;
-    v2 = v1;
-    ASSERT_EQ(v2.toInt(), 50);
-    ASSERT_EQ(v1.type(), v2.type());
-    ASSERT_FALSE(v2.isInfinity());
-    ASSERT_FALSE(v2.isNegativeInfinity());
-    ASSERT_FALSE(v2.isNaN());
-    ASSERT_TRUE(v2.isNumber());
-    ASSERT_FALSE(v2.isBool());
-    ASSERT_FALSE(v2.isString());
+    {
+        Value v1(50);
+        Value v2;
+        v2 = v1;
+        ASSERT_EQ(v2.toInt(), v1.toInt());
+        ASSERT_EQ(v1.type(), v2.type());
+        ASSERT_FALSE(v2.isInfinity());
+        ASSERT_FALSE(v2.isNegativeInfinity());
+        ASSERT_FALSE(v2.isNaN());
+        ASSERT_TRUE(v2.isNumber());
+        ASSERT_FALSE(v2.isBool());
+        ASSERT_FALSE(v2.isString());
+    }
+
+    {
+        Value v1(5.5);
+        Value v2;
+        v2 = v1;
+        ASSERT_EQ(v1.toDouble(), v2.toDouble());
+        ASSERT_EQ(v1.type(), v2.type());
+        ASSERT_FALSE(v2.isInfinity());
+        ASSERT_FALSE(v2.isNegativeInfinity());
+        ASSERT_FALSE(v2.isNaN());
+        ASSERT_TRUE(v2.isNumber());
+        ASSERT_FALSE(v2.isBool());
+        ASSERT_FALSE(v2.isString());
+    }
+
+    {
+        Value v1(true);
+        Value v2;
+        v2 = v1;
+        ASSERT_EQ(v1.toBool(), v2.toBool());
+        ASSERT_EQ(v1.type(), v2.type());
+        ASSERT_FALSE(v2.isInfinity());
+        ASSERT_FALSE(v2.isNegativeInfinity());
+        ASSERT_FALSE(v2.isNaN());
+        ASSERT_FALSE(v2.isNumber());
+        ASSERT_TRUE(v2.isBool());
+        ASSERT_FALSE(v2.isString());
+    }
+
+    {
+        Value v1("test");
+        Value v2;
+        v2 = v1;
+        ASSERT_EQ(v1.toString(), v2.toString());
+        ASSERT_EQ(v1.type(), v2.type());
+        ASSERT_FALSE(v2.isInfinity());
+        ASSERT_FALSE(v2.isNegativeInfinity());
+        ASSERT_FALSE(v2.isNaN());
+        ASSERT_FALSE(v2.isNumber());
+        ASSERT_FALSE(v2.isBool());
+        ASSERT_TRUE(v2.isString());
+    }
+
+    {
+        Value v1(Value::SpecialValue::NaN);
+        Value v2;
+        v2 = v1;
+        ASSERT_EQ(v2.toDouble(), 0);
+        ASSERT_EQ(v1.type(), v2.type());
+        ASSERT_FALSE(v2.isInfinity());
+        ASSERT_FALSE(v2.isNegativeInfinity());
+        ASSERT_TRUE(v2.isNaN());
+        ASSERT_FALSE(v2.isNumber());
+        ASSERT_FALSE(v2.isBool());
+        ASSERT_FALSE(v2.isString());
+    }
+
+    {
+        Value v1(Value::SpecialValue::Infinity);
+        Value v2;
+        v2 = v1;
+        ASSERT_EQ(v1.type(), v2.type());
+        ASSERT_TRUE(v2.isInfinity());
+        ASSERT_FALSE(v2.isNegativeInfinity());
+        ASSERT_FALSE(v2.isNaN());
+        ASSERT_FALSE(v2.isNumber());
+        ASSERT_FALSE(v2.isBool());
+        ASSERT_FALSE(v2.isString());
+    }
+
+    {
+        Value v1(Value::SpecialValue::NegativeInfinity);
+        Value v2;
+        v2 = v1;
+        ASSERT_EQ(v1.type(), v2.type());
+        ASSERT_FALSE(v2.isInfinity());
+        ASSERT_TRUE(v2.isNegativeInfinity());
+        ASSERT_FALSE(v2.isNaN());
+        ASSERT_FALSE(v2.isNumber());
+        ASSERT_FALSE(v2.isBool());
+        ASSERT_FALSE(v2.isString());
+    }
 }
 
 TEST(ValueTest, ToInt)

From bfe2c24c47a0e69895e7c0d15cb6c8c2e7ec6473 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Mon, 28 Aug 2023 17:16:05 +0200
Subject: [PATCH 05/56] Add API for top level reporter blocks

---
 include/scratchcpp/block.h          |  6 +++++
 src/scratch/block.cpp               | 35 +++++++++++++++++++++++++++++
 src/scratch/block_p.h               |  3 +++
 test/scratch_classes/block_test.cpp | 17 ++++++++++++++
 4 files changed, 61 insertions(+)

diff --git a/include/scratchcpp/block.h b/include/scratchcpp/block.h
index 4288f808..11660ab0 100644
--- a/include/scratchcpp/block.h
+++ b/include/scratchcpp/block.h
@@ -12,6 +12,7 @@ class IEngine;
 class Target;
 class Input;
 class Field;
+class InputValue;
 class BlockPrivate;
 
 /*! \brief The Block class represents a Scratch block. */
@@ -68,6 +69,11 @@ class LIBSCRATCHCPP_EXPORT Block : public Entity
 
         BlockPrototype *mutationPrototype();
 
+        bool isTopLevelReporter() const;
+        void setIsTopLevelReporter(bool isTopLevelReporter);
+
+        InputValue *topLevelReporterInfo();
+
     private:
         spimpl::unique_impl_ptr<BlockPrivate> impl;
 };
diff --git a/src/scratch/block.cpp b/src/scratch/block.cpp
index 92e1ee8e..fbaa7c2a 100644
--- a/src/scratch/block.cpp
+++ b/src/scratch/block.cpp
@@ -58,6 +58,41 @@ BlockPrototype *Block::mutationPrototype()
     return &impl->mutationPrototype;
 }
 
+/*! Returns true if this is a top level reporter block for a variable or a list. */
+bool Block::isTopLevelReporter() const
+{
+    return impl->isTopLevelReporter;
+}
+
+/*!
+ * Sets whether this block is a top level reporter block for a variable or a list.
+ * \note Setting this to true will allow the use topLevelReporterInfo().
+ */
+void Block::setIsTopLevelReporter(bool isTopLevelReporter)
+{
+    impl->isTopLevelReporter = isTopLevelReporter;
+
+    if (isTopLevelReporter && !impl->topLevelReporterInfo)
+        impl->topLevelReporterInfo = std::make_unique<InputValue>(InputValue::Type::Variable);
+    else if (impl->topLevelReporterInfo) {
+        impl->topLevelReporterInfo.reset();
+        impl->topLevelReporterInfo = nullptr;
+    }
+}
+
+/*!
+ * Returns the information about this top level reporter block (if this is a top level reporter block).
+ * \note This function will return nullptr if this isn't a top level reporter block. Use setIsTopLevelReporter(true)
+ * before using this function.
+ */
+InputValue *Block::topLevelReporterInfo()
+{
+    if (impl->topLevelReporterInfo)
+        return impl->topLevelReporterInfo.get();
+    else
+        return nullptr;
+}
+
 /*! Returns the next block. */
 std::shared_ptr<Block> Block::next() const
 {
diff --git a/src/scratch/block_p.h b/src/scratch/block_p.h
index 0c0daff3..082e24a2 100644
--- a/src/scratch/block_p.h
+++ b/src/scratch/block_p.h
@@ -3,6 +3,7 @@
 #pragma once
 
 #include <scratchcpp/blockprototype.h>
+#include <scratchcpp/inputvalue.h>
 
 namespace libscratchcpp
 {
@@ -33,6 +34,8 @@ struct BlockPrivate
         Target *target = nullptr;
         BlockPrototype mutationPrototype;
         bool mutationHasNext = true;
+        bool isTopLevelReporter = false;
+        std::unique_ptr<InputValue> topLevelReporterInfo = nullptr;
 };
 
 } // namespace libscratchcpp
diff --git a/test/scratch_classes/block_test.cpp b/test/scratch_classes/block_test.cpp
index 0dcf626b..14430946 100644
--- a/test/scratch_classes/block_test.cpp
+++ b/test/scratch_classes/block_test.cpp
@@ -241,3 +241,20 @@ TEST_F(BlockTest, MutationPrototype)
     Block block("", "");
     ASSERT_TRUE(block.mutationPrototype());
 }
+
+TEST_F(BlockTest, TopLevelReporter)
+{
+    Block block("", "");
+    ASSERT_FALSE(block.isTopLevelReporter());
+    ASSERT_EQ(block.topLevelReporterInfo(), nullptr);
+
+    for (int i = 0; i < 2; i++) {
+        block.setIsTopLevelReporter(true);
+        ASSERT_TRUE(block.isTopLevelReporter());
+        ASSERT_TRUE(block.topLevelReporterInfo());
+
+        block.setIsTopLevelReporter(false);
+        ASSERT_FALSE(block.isTopLevelReporter());
+        ASSERT_EQ(block.topLevelReporterInfo(), nullptr);
+    }
+}

From 20d5c55e86cb7d8561835bfc2fba0fd999efa42d Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Mon, 28 Aug 2023 17:16:51 +0200
Subject: [PATCH 06/56] Load top level reporter blocks

---
 src/internal/scratch3reader.cpp         |  15 ++++++--
 test/load_project/load_project_test.cpp |  46 ++++++++++++++++++++++++
 test/top_level_reporter.sb3             | Bin 0 -> 2054 bytes
 3 files changed, 59 insertions(+), 2 deletions(-)
 create mode 100644 test/top_level_reporter.sb3

diff --git a/src/internal/scratch3reader.cpp b/src/internal/scratch3reader.cpp
index 2223c8b0..d49cf859 100644
--- a/src/internal/scratch3reader.cpp
+++ b/src/internal/scratch3reader.cpp
@@ -79,11 +79,22 @@ bool Scratch3Reader::load()
             auto blocks = jsonTarget["blocks"];
             for (json::iterator it = blocks.begin(); it != blocks.end(); ++it) {
                 auto blockInfo = it.value();
+
                 if (blockInfo.is_array()) {
-                    // TODO: Find out what these "array" blocks that look like input values are for
-                    // Let's ignore them for now...
+                    // This is a top level reporter block for a variable/list
+                    READER_STEP(step, "target -> block -> top level reporter info");
+                    auto block = std::make_shared<Block>(it.key(), "");
+                    block->setIsTopLevelReporter(true);
+                    InputValue *reporterInfo = block->topLevelReporterInfo();
+
+                    reporterInfo->setValue(jsonToValue(blockInfo[1]));
+                    reporterInfo->setValueId(jsonToValue(blockInfo[2]).toString());
+                    reporterInfo->setType(static_cast<InputValue::Type>(blockInfo[0]));
+
+                    target->addBlock(block);
                     continue;
                 }
+
                 READER_STEP(step, "target -> block -> opcode");
                 auto block = std::make_shared<Block>(it.key(), blockInfo["opcode"]);
                 std::string nextId;
diff --git a/test/load_project/load_project_test.cpp b/test/load_project/load_project_test.cpp
index 4585ec29..6b540883 100644
--- a/test/load_project/load_project_test.cpp
+++ b/test/load_project/load_project_test.cpp
@@ -10,6 +10,7 @@
 #include <scratchcpp/costume.h>
 #include <scratchcpp/sound.h>
 #include <scratchcpp/sprite.h>
+#include <scratchcpp/inputvalue.h>
 
 #include "../common.h"
 
@@ -124,17 +125,20 @@ TEST(LoadProjectTest, LoadTestProject)
         // Sprite1: blocks
         ASSERT_EQ(sprite1->greenFlagBlocks().size(), 1);
         ASSERT_TRUE(sprite1->greenFlagBlocks()[0]);
+        ASSERT_FALSE(sprite1->greenFlagBlocks()[0]->isTopLevelReporter());
         ASSERT_EQ(sprite1->greenFlagBlocks()[0]->opcode(), "event_whenflagclicked");
         auto block = sprite1->greenFlagBlocks()[0]->next();
         ASSERT_TRUE(block);
         ASSERT_EQ(block->parent(), sprite1->greenFlagBlocks()[0]);
         ASSERT_EQ(block->opcode(), "control_forever");
+        ASSERT_FALSE(block->isTopLevelReporter());
         ASSERT_INPUT(block, "SUBSTACK");
         block = GET_INPUT(block, "SUBSTACK")->valueBlock();
         ASSERT_TRUE(block);
 
         ASSERT_EQ(block->opcode(), "motion_goto");
         ASSERT_INPUT(block, "TO");
+        ASSERT_FALSE(block->isTopLevelReporter());
         auto inputBlock = GET_INPUT(block, "TO")->valueBlock();
         ASSERT_TRUE(inputBlock);
         ASSERT_EQ(inputBlock->opcode(), "motion_goto_menu");
@@ -155,6 +159,7 @@ TEST(LoadProjectTest, LoadTestProject)
         inputBlock = GET_INPUT(block, "VALUE")->valueBlock();
         ASSERT_TRUE(inputBlock);
         ASSERT_EQ(inputBlock->opcode(), "operator_random");
+        ASSERT_FALSE(inputBlock->isTopLevelReporter());
         ASSERT_INPUT(inputBlock, "FROM");
         ASSERT_EQ(GET_INPUT(inputBlock, "FROM")->primaryValue()->value().toInt(), 1);
         ASSERT_INPUT(inputBlock, "TO");
@@ -250,6 +255,47 @@ TEST(LoadProjectTest, LoadTestProject)
     }
 }
 
+TEST(LoadProjectTest, LoadTopLevelReporterProject)
+{
+    int i = 0;
+    for (auto version : scratchVersions) {
+        Project p("top_level_reporter" + fileExtensions[i], version);
+
+        ASSERT_TRUE(p.load());
+
+        auto engine = p.engine();
+        ASSERT_EQ(engine->targets().size(), 2);
+
+        // Sprite1
+        ASSERT_NE(engine->findTarget("Sprite1"), -1);
+        Sprite *sprite1 = dynamic_cast<Sprite *>(engine->targetAt(engine->findTarget("Sprite1")));
+        ASSERT_EQ(sprite1->blocks().size(), 2);
+
+        // Sprite1: blocks
+        auto block1 = sprite1->blockAt(0);
+        ASSERT_TRUE(block1);
+        ASSERT_TRUE(block1->isTopLevelReporter());
+        InputValue *reporterInfo = block1->topLevelReporterInfo();
+        ASSERT_TRUE(reporterInfo);
+        ASSERT_EQ(reporterInfo->type(), InputValue::Type::Variable);
+        ASSERT_EQ(reporterInfo->value(), "var");
+        ASSERT_EQ(reporterInfo->valueId(), "b[Pq/qD6PJ+hpWtz;X`G");
+        ASSERT_EQ(reporterInfo->valuePtr(), sprite1->variableAt(0));
+
+        auto block2 = sprite1->blockAt(1);
+        ASSERT_TRUE(block2);
+        ASSERT_TRUE(block2->isTopLevelReporter());
+        reporterInfo = block2->topLevelReporterInfo();
+        ASSERT_TRUE(reporterInfo);
+        ASSERT_EQ(reporterInfo->type(), InputValue::Type::List);
+        ASSERT_EQ(reporterInfo->value(), "list");
+        ASSERT_EQ(reporterInfo->valueId(), "*BW*~d~DgcZee0q=v*T2");
+        ASSERT_EQ(reporterInfo->valuePtr(), sprite1->listAt(0));
+
+        i++;
+    }
+}
+
 TEST(LoadProjectTest, ProjectTest)
 {
     int i = 0;
diff --git a/test/top_level_reporter.sb3 b/test/top_level_reporter.sb3
new file mode 100644
index 0000000000000000000000000000000000000000..bafefdd1dfcb52e4645e3021ae4d3c086df75e14
GIT binary patch

From 97819b31656361a11ecee4ed6a17c607de4ba8de Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Tue, 29 Aug 2023 11:24:58 +0200
Subject: [PATCH 07/56] Fix a typo in Block::setIsTopLevelReporter()

---
 src/scratch/block.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/scratch/block.cpp b/src/scratch/block.cpp
index fbaa7c2a..619950ac 100644
--- a/src/scratch/block.cpp
+++ b/src/scratch/block.cpp
@@ -66,7 +66,7 @@ bool Block::isTopLevelReporter() const
 
 /*!
  * Sets whether this block is a top level reporter block for a variable or a list.
- * \note Setting this to true will allow the use topLevelReporterInfo().
+ * \note Setting this to true will allow the use of topLevelReporterInfo().
  */
 void Block::setIsTopLevelReporter(bool isTopLevelReporter)
 {

From 146a4bfc81f3303f862cf97b8c015ab486745165 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Tue, 29 Aug 2023 11:39:20 +0200
Subject: [PATCH 08/56] Use Entity as the base class of Asset

And use shared_ptr for assets
---
 include/scratchcpp/asset.h              |  8 ++---
 include/scratchcpp/costume.h            |  1 +
 include/scratchcpp/sound.h              |  1 +
 include/scratchcpp/target.h             | 12 +++----
 src/internal/scratch3reader.cpp         | 14 ++++----
 src/scratch/asset.cpp                   | 11 +++---
 src/scratch/asset_p.cpp                 | 10 +++---
 src/scratch/asset_p.h                   |  5 ++-
 src/scratch/target.cpp                  | 45 +++++++------------------
 src/scratch/target_p.h                  |  4 +--
 test/assets/asset_test.cpp              | 11 +++++-
 test/assets/costume_test.cpp            |  2 +-
 test/assets/sound_test.cpp              |  2 +-
 test/load_project/load_project_test.cpp | 24 ++++++-------
 test/scratch_classes/target_test.cpp    | 36 ++++++++++----------
 15 files changed, 89 insertions(+), 97 deletions(-)

diff --git a/include/scratchcpp/asset.h b/include/scratchcpp/asset.h
index 48581f67..b1d4182a 100644
--- a/include/scratchcpp/asset.h
+++ b/include/scratchcpp/asset.h
@@ -2,9 +2,8 @@
 
 #pragma once
 
-#include <string>
+#include "entity.h"
 #include "spimpl.h"
-
 #include "global.h"
 
 namespace libscratchcpp
@@ -13,12 +12,13 @@ namespace libscratchcpp
 class AssetPrivate;
 
 /*! \brief The Asset class represents a Scratch asset, for example a Costume or a Sound. */
-class LIBSCRATCHCPP_EXPORT Asset
+class LIBSCRATCHCPP_EXPORT Asset : public Entity
 {
     public:
         Asset(const std::string &name, const std::string &id, const std::string &format);
+        Asset(const Asset &) = delete;
 
-        const std::string &assetId() const;
+        void setId(const std::string &id);
 
         const std::string &name() const;
 
diff --git a/include/scratchcpp/costume.h b/include/scratchcpp/costume.h
index 652cb554..e44d4184 100644
--- a/include/scratchcpp/costume.h
+++ b/include/scratchcpp/costume.h
@@ -16,6 +16,7 @@ class LIBSCRATCHCPP_EXPORT Costume : public Asset
 {
     public:
         Costume(const std::string &name, const std::string &id, const std::string &format);
+        Costume(const Costume &) = delete;
 
         double bitmapResolution() const;
         void setBitmapResolution(double newBitmapResolution);
diff --git a/include/scratchcpp/sound.h b/include/scratchcpp/sound.h
index 0604f601..8f9eac41 100644
--- a/include/scratchcpp/sound.h
+++ b/include/scratchcpp/sound.h
@@ -16,6 +16,7 @@ class LIBSCRATCHCPP_EXPORT Sound : public Asset
 {
     public:
         Sound(const std::string &name, const std::string &id, const std::string &format);
+        Sound(const Sound &) = delete;
 
         int rate() const;
         void setRate(int newRate);
diff --git a/include/scratchcpp/target.h b/include/scratchcpp/target.h
index 4829c22b..3a9b01a7 100644
--- a/include/scratchcpp/target.h
+++ b/include/scratchcpp/target.h
@@ -51,14 +51,14 @@ class LIBSCRATCHCPP_EXPORT Target
         int currentCostume() const;
         void setCurrentCostume(int newCostume);
 
-        const std::vector<Costume> &costumes() const;
-        int addCostume(const Costume &costume);
-        const Costume &costumeAt(int index) const;
+        const std::vector<std::shared_ptr<Costume>> &costumes() const;
+        int addCostume(std::shared_ptr<Costume> costume);
+        std::shared_ptr<Costume> costumeAt(int index) const;
         int findCostume(const std::string &costumeName) const;
 
-        const std::vector<Sound> &sounds() const;
-        int addSound(const Sound &sound);
-        const Sound &soundAt(int index) const;
+        const std::vector<std::shared_ptr<Sound>> &sounds() const;
+        int addSound(std::shared_ptr<Sound> sound);
+        std::shared_ptr<Sound> soundAt(int index) const;
         int findSound(const std::string &soundName) const;
 
         int layerOrder() const;
diff --git a/src/internal/scratch3reader.cpp b/src/internal/scratch3reader.cpp
index d49cf859..052bf238 100644
--- a/src/internal/scratch3reader.cpp
+++ b/src/internal/scratch3reader.cpp
@@ -203,14 +203,14 @@ bool Scratch3Reader::load()
             auto costumes = jsonTarget["costumes"];
             for (auto jsonCostume : costumes) {
                 READER_STEP(step, "target -> costume -> { name, assetId, dataFormat }");
-                Costume costume(jsonCostume["name"], jsonCostume["assetId"], jsonCostume["dataFormat"]);
+                auto costume = std::make_shared<Costume>(jsonCostume["name"], jsonCostume["assetId"], jsonCostume["dataFormat"]);
                 READER_STEP(step, "target -> costume -> bitmapResolution");
                 if (jsonCostume.contains("bitmapResolution"))
-                    costume.setBitmapResolution(jsonCostume["bitmapResolution"]);
+                    costume->setBitmapResolution(jsonCostume["bitmapResolution"]);
                 READER_STEP(step, "target -> costume -> rotationCenterX");
-                costume.setRotationCenterX(jsonCostume["rotationCenterX"]);
+                costume->setRotationCenterX(jsonCostume["rotationCenterX"]);
                 READER_STEP(step, "target -> costume -> rotationCenterY");
-                costume.setRotationCenterY(jsonCostume["rotationCenterY"]);
+                costume->setRotationCenterY(jsonCostume["rotationCenterY"]);
                 target->addCostume(costume);
             }
 
@@ -219,11 +219,11 @@ bool Scratch3Reader::load()
             auto sounds = jsonTarget["sounds"];
             for (auto jsonSound : sounds) {
                 READER_STEP(step, "target -> sound -> { name, assetId, dataFormat }");
-                Sound sound(jsonSound["name"], jsonSound["assetId"], jsonSound["dataFormat"]);
+                auto sound = std::make_shared<Sound>(jsonSound["name"], jsonSound["assetId"], jsonSound["dataFormat"]);
                 READER_STEP(step, "target -> sound -> rate");
-                sound.setRate(jsonSound["rate"]);
+                sound->setRate(jsonSound["rate"]);
                 READER_STEP(step, "target -> sound -> sampleCount");
-                sound.setSampleCount(jsonSound["sampleCount"]);
+                sound->setSampleCount(jsonSound["sampleCount"]);
                 target->addSound(sound);
             }
 
diff --git a/src/scratch/asset.cpp b/src/scratch/asset.cpp
index 78c43937..dddf95e6 100644
--- a/src/scratch/asset.cpp
+++ b/src/scratch/asset.cpp
@@ -8,14 +8,17 @@ using namespace libscratchcpp;
 
 /*! Constructs Asset. */
 Asset::Asset(const std::string &name, const std::string &id, const std::string &format) :
-    impl(spimpl::make_impl<AssetPrivate>(name, id, format))
+    Entity(id),
+    impl(spimpl::make_impl<AssetPrivate>(name, format))
 {
+    impl->updateFileName(id);
 }
 
-/*! Returns the MD5 hash of the asset file. */
-const std::string &Asset::assetId() const
+/*! Sets the ID (MD5 hash) of the asset file. */
+void Asset::setId(const std::string &id)
 {
-    return impl->assetId;
+    Entity::setId(id);
+    impl->updateFileName(id);
 }
 
 /*! Returns the name of the asset. */
diff --git a/src/scratch/asset_p.cpp b/src/scratch/asset_p.cpp
index 92276dce..5b06b874 100644
--- a/src/scratch/asset_p.cpp
+++ b/src/scratch/asset_p.cpp
@@ -4,16 +4,14 @@
 
 using namespace libscratchcpp;
 
-AssetPrivate::AssetPrivate(const std::string &name, const std::string &id, const std::string &format) :
+AssetPrivate::AssetPrivate(const std::string &name, const std::string &format) :
     name(name),
-    assetId(id),
     dataFormat(format)
 {
-    updateFileName();
 }
 
-void AssetPrivate::updateFileName()
+void AssetPrivate::updateFileName(const std::string &id)
 {
-    // NOTE: fileName depends on assetId and dataFormat
-    fileName = assetId + "." + dataFormat;
+    // NOTE: fileName depends on id and dataFormat
+    fileName = id + "." + dataFormat;
 }
diff --git a/src/scratch/asset_p.h b/src/scratch/asset_p.h
index 42023799..00b97193 100644
--- a/src/scratch/asset_p.h
+++ b/src/scratch/asset_p.h
@@ -9,11 +9,10 @@ namespace libscratchcpp
 
 struct AssetPrivate
 {
-        AssetPrivate(const std::string &name, const std::string &id, const std::string &format);
+        AssetPrivate(const std::string &name, const std::string &format);
 
-        void updateFileName();
+        void updateFileName(const std::string &id);
 
-        std::string assetId;
         std::string name;
         std::string dataFormat;
         std::string fileName;
diff --git a/src/scratch/target.cpp b/src/scratch/target.cpp
index fcfe20f0..e0413a1b 100644
--- a/src/scratch/target.cpp
+++ b/src/scratch/target.cpp
@@ -193,35 +193,25 @@ void Target::setCurrentCostume(int newCostume)
 }
 
 /*! Returns the list of costumes. */
-const std::vector<Costume> &Target::costumes() const
+const std::vector<std::shared_ptr<Costume>> &Target::costumes() const
 {
     return impl->costumes;
 }
 
 /*! Adds a costume and returns its index. */
-int Target::addCostume(const Costume &costume)
+int Target::addCostume(std::shared_ptr<Costume> costume)
 {
-    // TODO: Use shared_ptr for assets
-    /*auto it = std::find(impl->costumes.begin(), impl->costumes.end(), costume);
+    auto it = std::find(impl->costumes.begin(), impl->costumes.end(), costume);
 
-    if (it != impl->blocks.end())
-        return it - impl->blocks.begin();*/
-
-    int i = 0;
-
-    for (const auto &c : impl->costumes) {
-        if (c.name() == costume.name())
-            return i;
-
-        i++;
-    }
+    if (it != impl->costumes.end())
+        return it - impl->costumes.begin();
 
     impl->costumes.push_back(costume);
     return impl->costumes.size() - 1;
 }
 
 /*! Returns the costume at index. */
-const libscratchcpp::Costume &Target::costumeAt(int index) const
+std::shared_ptr<Costume> Target::costumeAt(int index) const
 {
     // TODO: Add range check
     return impl->costumes[index];
@@ -232,7 +222,7 @@ int Target::findCostume(const std::string &costumeName) const
 {
     int i = 0;
     for (auto costume : impl->costumes) {
-        if (costume.name() == costumeName)
+        if (costume->name() == costumeName)
             return i;
         i++;
     }
@@ -240,34 +230,25 @@ int Target::findCostume(const std::string &costumeName) const
 }
 
 /*! Returns the list of sounds. */
-const std::vector<Sound> &Target::sounds() const
+const std::vector<std::shared_ptr<Sound>> &Target::sounds() const
 {
     return impl->sounds;
 }
 
 /*! Adds a sound and returns its index. */
-int Target::addSound(const Sound &sound)
+int Target::addSound(std::shared_ptr<Sound> sound)
 {
-    // TODO: Use shared_ptr for assets
-    /*auto it = std::find(impl->sounds.begin(), impl->sounds.end(), sound);
+    auto it = std::find(impl->sounds.begin(), impl->sounds.end(), sound);
 
     if (it != impl->sounds.end())
-        return it - impl->sounds.begin();*/
-
-    int i = 0;
+        return it - impl->sounds.begin();
 
-    for (const auto &s : impl->sounds) {
-        if (s.name() == sound.name())
-            return i;
-
-        i++;
-    }
     impl->sounds.push_back(sound);
     return impl->sounds.size() - 1;
 }
 
 /*! Returns the sound at index. */
-const libscratchcpp::Sound &Target::soundAt(int index) const
+std::shared_ptr<Sound> Target::soundAt(int index) const
 {
     // TODO: Add range check
     return impl->sounds[index];
@@ -278,7 +259,7 @@ int Target::findSound(const std::string &soundName) const
 {
     int i = 0;
     for (auto sound : impl->sounds) {
-        if (sound.name() == soundName)
+        if (sound->name() == soundName)
             return i;
         i++;
     }
diff --git a/src/scratch/target_p.h b/src/scratch/target_p.h
index 62381296..1a990b23 100644
--- a/src/scratch/target_p.h
+++ b/src/scratch/target_p.h
@@ -25,8 +25,8 @@ struct TargetPrivate
         std::vector<std::shared_ptr<List>> lists;
         std::vector<std::shared_ptr<Block>> blocks;
         int currentCostume = 1;
-        std::vector<Costume> costumes;
-        std::vector<Sound> sounds;
+        std::vector<std::shared_ptr<Costume>> costumes;
+        std::vector<std::shared_ptr<Sound>> sounds;
         int layerOrder = 0;
         int volume = 100;
 };
diff --git a/test/assets/asset_test.cpp b/test/assets/asset_test.cpp
index 70a426d4..083a5674 100644
--- a/test/assets/asset_test.cpp
+++ b/test/assets/asset_test.cpp
@@ -7,8 +7,17 @@ using namespace libscratchcpp;
 TEST(AssetTest, Constructors)
 {
     Asset asset("costume1", "a", "svg");
-    ASSERT_EQ(asset.assetId(), "a");
+    ASSERT_EQ(asset.id(), "a");
     ASSERT_EQ(asset.name(), "costume1");
     ASSERT_EQ(asset.dataFormat(), "svg");
     ASSERT_EQ(asset.md5ext(), "a.svg");
 }
+
+TEST(AssetTest, Id)
+{
+    Asset asset("costume1", "a", "svg");
+
+    asset.setId("b");
+    ASSERT_EQ(asset.id(), "b");
+    ASSERT_EQ(asset.md5ext(), "b.svg");
+}
diff --git a/test/assets/costume_test.cpp b/test/assets/costume_test.cpp
index b452b209..f180b43b 100644
--- a/test/assets/costume_test.cpp
+++ b/test/assets/costume_test.cpp
@@ -7,7 +7,7 @@ using namespace libscratchcpp;
 TEST(CostumeTest, Constructors)
 {
     Costume costume("costume1", "a", "svg");
-    ASSERT_EQ(costume.assetId(), "a");
+    ASSERT_EQ(costume.id(), "a");
     ASSERT_EQ(costume.name(), "costume1");
     ASSERT_EQ(costume.dataFormat(), "svg");
     ASSERT_EQ(costume.md5ext(), "a.svg");
diff --git a/test/assets/sound_test.cpp b/test/assets/sound_test.cpp
index 16670040..811af433 100644
--- a/test/assets/sound_test.cpp
+++ b/test/assets/sound_test.cpp
@@ -7,7 +7,7 @@ using namespace libscratchcpp;
 TEST(SoundTest, Constructors)
 {
     Sound sound("sound1", "a", "svg");
-    ASSERT_EQ(sound.assetId(), "a");
+    ASSERT_EQ(sound.id(), "a");
     ASSERT_EQ(sound.name(), "sound1");
     ASSERT_EQ(sound.dataFormat(), "svg");
     ASSERT_EQ(sound.md5ext(), "a.svg");
diff --git a/test/load_project/load_project_test.cpp b/test/load_project/load_project_test.cpp
index 6b540883..4329b102 100644
--- a/test/load_project/load_project_test.cpp
+++ b/test/load_project/load_project_test.cpp
@@ -50,10 +50,10 @@ TEST(LoadProjectTest, EmptyProject)
         ASSERT_TRUE(stage->textToSpeechLanguage().empty());
 
         auto backdrop = stage->costumeAt(0);
-        ASSERT_EQ(backdrop.name(), "backdrop1");
-        ASSERT_FALSE(backdrop.assetId().empty());
-        ASSERT_EQ(backdrop.md5ext(), backdrop.assetId() + ".svg");
-        ASSERT_EQ(backdrop.dataFormat(), "svg");
+        ASSERT_EQ(backdrop->name(), "backdrop1");
+        ASSERT_FALSE(backdrop->id().empty());
+        ASSERT_EQ(backdrop->md5ext(), backdrop->id() + ".svg");
+        ASSERT_EQ(backdrop->dataFormat(), "svg");
 
         i++;
     }
@@ -109,10 +109,10 @@ TEST(LoadProjectTest, LoadTestProject)
 
         // Sprite1: sounds
         auto sound = sprite1->soundAt(0);
-        ASSERT_EQ(sound.name(), "Meow");
-        ASSERT_FALSE(sound.assetId().empty());
-        ASSERT_EQ(sound.md5ext(), sound.assetId() + ".wav");
-        ASSERT_EQ(sound.dataFormat(), "wav");
+        ASSERT_EQ(sound->name(), "Meow");
+        ASSERT_FALSE(sound->id().empty());
+        ASSERT_EQ(sound->md5ext(), sound->id() + ".wav");
+        ASSERT_EQ(sound->dataFormat(), "wav");
 
         // Sprite1: variables
         ASSERT_VAR(sprite1, "var2");
@@ -238,10 +238,10 @@ TEST(LoadProjectTest, LoadTestProject)
 
         // Balloon1: sounds
         sound = sprite2->soundAt(0);
-        ASSERT_EQ(sound.name(), "Pop");
-        ASSERT_FALSE(sound.assetId().empty());
-        ASSERT_EQ(sound.md5ext(), sound.assetId() + ".wav");
-        ASSERT_EQ(sound.dataFormat(), "wav");
+        ASSERT_EQ(sound->name(), "Pop");
+        ASSERT_FALSE(sound->id().empty());
+        ASSERT_EQ(sound->md5ext(), sound->id() + ".wav");
+        ASSERT_EQ(sound->dataFormat(), "wav");
 
         // Balloon1: variables
         ASSERT_VAR(sprite2, "var2");
diff --git a/test/scratch_classes/target_test.cpp b/test/scratch_classes/target_test.cpp
index 10855772..4112f12d 100644
--- a/test/scratch_classes/target_test.cpp
+++ b/test/scratch_classes/target_test.cpp
@@ -123,9 +123,9 @@ TEST(TargetTest, CurrentCostume)
 
 TEST(TargetTest, Costumes)
 {
-    Costume c1("costume1", "", "svg");
-    Costume c2("costume2", "", "png");
-    Costume c3("costume3", "", "svg");
+    auto c1 = std::make_shared<Costume>("costume1", "", "svg");
+    auto c2 = std::make_shared<Costume>("costume2", "", "png");
+    auto c3 = std::make_shared<Costume>("costume3", "", "svg");
 
     Target target;
     ASSERT_EQ(target.addCostume(c1), 0);
@@ -136,13 +136,13 @@ TEST(TargetTest, Costumes)
     // TODO: Use shared_ptr for assets
     // ASSERT_EQ(target.costumes(), std::vector<std::shared_ptr<Costume>>({ c1, c2, c3 }));
     ASSERT_EQ(target.costumes().size(), 3);
-    ASSERT_EQ(target.costumes()[0].name(), c1.name());
-    ASSERT_EQ(target.costumes()[1].name(), c2.name());
-    ASSERT_EQ(target.costumes()[2].name(), c3.name());
+    ASSERT_EQ(target.costumes()[0]->name(), c1->name());
+    ASSERT_EQ(target.costumes()[1]->name(), c2->name());
+    ASSERT_EQ(target.costumes()[2]->name(), c3->name());
 
-    ASSERT_EQ(target.costumeAt(0).name(), c1.name());
-    ASSERT_EQ(target.costumeAt(1).name(), c2.name());
-    ASSERT_EQ(target.costumeAt(2).name(), c3.name());
+    ASSERT_EQ(target.costumeAt(0)->name(), c1->name());
+    ASSERT_EQ(target.costumeAt(1)->name(), c2->name());
+    ASSERT_EQ(target.costumeAt(2)->name(), c3->name());
 
     ASSERT_EQ(target.findCostume("invalid"), -1);
     ASSERT_EQ(target.findCostume("costume1"), 0);
@@ -152,9 +152,9 @@ TEST(TargetTest, Costumes)
 
 TEST(TargetTest, Sounds)
 {
-    Sound s1("sound1", "", "mp3");
-    Sound s2("sound2", "", "wav");
-    Sound s3("sound3", "", "mp3");
+    auto s1 = std::make_shared<Sound>("sound1", "", "mp3");
+    auto s2 = std::make_shared<Sound>("sound2", "", "wav");
+    auto s3 = std::make_shared<Sound>("sound3", "", "mp3");
 
     Target target;
     ASSERT_EQ(target.addSound(s1), 0);
@@ -165,13 +165,13 @@ TEST(TargetTest, Sounds)
     // TODO: Use shared_ptr for assets
     // ASSERT_EQ(target.sounds(), std::vector<std::shared_ptr<Sound>>({ s1, s2, s3 }));
     ASSERT_EQ(target.sounds().size(), 3);
-    ASSERT_EQ(target.sounds()[0].name(), s1.name());
-    ASSERT_EQ(target.sounds()[1].name(), s2.name());
-    ASSERT_EQ(target.sounds()[2].name(), s3.name());
+    ASSERT_EQ(target.sounds()[0]->name(), s1->name());
+    ASSERT_EQ(target.sounds()[1]->name(), s2->name());
+    ASSERT_EQ(target.sounds()[2]->name(), s3->name());
 
-    ASSERT_EQ(target.soundAt(0).name(), s1.name());
-    ASSERT_EQ(target.soundAt(1).name(), s2.name());
-    ASSERT_EQ(target.soundAt(2).name(), s3.name());
+    ASSERT_EQ(target.soundAt(0)->name(), s1->name());
+    ASSERT_EQ(target.soundAt(1)->name(), s2->name());
+    ASSERT_EQ(target.soundAt(2)->name(), s3->name());
 
     ASSERT_EQ(target.findSound("invalid"), -1);
     ASSERT_EQ(target.findSound("sound1"), 0);

From 07d06f43faadd4813f50d225626771e841918b52 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Tue, 29 Aug 2023 15:30:46 +0200
Subject: [PATCH 09/56] Make Asset private implementation unique

---
 include/scratchcpp/asset.h   | 2 +-
 include/scratchcpp/costume.h | 2 +-
 include/scratchcpp/sound.h   | 2 +-
 src/scratch/asset.cpp        | 2 +-
 src/scratch/asset_p.h        | 1 +
 src/scratch/costume.cpp      | 2 +-
 src/scratch/costume_p.h      | 1 +
 src/scratch/sound.cpp        | 2 +-
 src/scratch/sound_p.h        | 1 +
 9 files changed, 9 insertions(+), 6 deletions(-)

diff --git a/include/scratchcpp/asset.h b/include/scratchcpp/asset.h
index b1d4182a..ee6a1b43 100644
--- a/include/scratchcpp/asset.h
+++ b/include/scratchcpp/asset.h
@@ -27,7 +27,7 @@ class LIBSCRATCHCPP_EXPORT Asset : public Entity
         const std::string &dataFormat() const;
 
     private:
-        spimpl::impl_ptr<AssetPrivate> impl;
+        spimpl::unique_impl_ptr<AssetPrivate> impl;
 };
 
 } // namespace libscratchcpp
diff --git a/include/scratchcpp/costume.h b/include/scratchcpp/costume.h
index e44d4184..e81d26ce 100644
--- a/include/scratchcpp/costume.h
+++ b/include/scratchcpp/costume.h
@@ -28,7 +28,7 @@ class LIBSCRATCHCPP_EXPORT Costume : public Asset
         void setRotationCenterY(int newRotationCenterY);
 
     private:
-        spimpl::impl_ptr<CostumePrivate> impl;
+        spimpl::unique_impl_ptr<CostumePrivate> impl;
 };
 
 } // namespace libscratchcpp
diff --git a/include/scratchcpp/sound.h b/include/scratchcpp/sound.h
index 8f9eac41..4a77bff5 100644
--- a/include/scratchcpp/sound.h
+++ b/include/scratchcpp/sound.h
@@ -25,7 +25,7 @@ class LIBSCRATCHCPP_EXPORT Sound : public Asset
         void setSampleCount(int newSampleCount);
 
     private:
-        spimpl::impl_ptr<SoundPrivate> impl;
+        spimpl::unique_impl_ptr<SoundPrivate> impl;
 };
 
 } // namespace libscratchcpp
diff --git a/src/scratch/asset.cpp b/src/scratch/asset.cpp
index dddf95e6..a17977e0 100644
--- a/src/scratch/asset.cpp
+++ b/src/scratch/asset.cpp
@@ -9,7 +9,7 @@ using namespace libscratchcpp;
 /*! Constructs Asset. */
 Asset::Asset(const std::string &name, const std::string &id, const std::string &format) :
     Entity(id),
-    impl(spimpl::make_impl<AssetPrivate>(name, format))
+    impl(spimpl::make_unique_impl<AssetPrivate>(name, format))
 {
     impl->updateFileName(id);
 }
diff --git a/src/scratch/asset_p.h b/src/scratch/asset_p.h
index 00b97193..f1c2a16f 100644
--- a/src/scratch/asset_p.h
+++ b/src/scratch/asset_p.h
@@ -10,6 +10,7 @@ namespace libscratchcpp
 struct AssetPrivate
 {
         AssetPrivate(const std::string &name, const std::string &format);
+        AssetPrivate(const AssetPrivate &) = delete;
 
         void updateFileName(const std::string &id);
 
diff --git a/src/scratch/costume.cpp b/src/scratch/costume.cpp
index bb16b6fa..1d46d007 100644
--- a/src/scratch/costume.cpp
+++ b/src/scratch/costume.cpp
@@ -9,7 +9,7 @@ using namespace libscratchcpp;
 /*! Constructs Costume. */
 Costume::Costume(const std::string &name, const std::string &id, const std::string &format) :
     Asset(name, id, format),
-    impl(spimpl::make_impl<CostumePrivate>())
+    impl(spimpl::make_unique_impl<CostumePrivate>())
 {
 }
 
diff --git a/src/scratch/costume_p.h b/src/scratch/costume_p.h
index cc13193c..3d37f4bb 100644
--- a/src/scratch/costume_p.h
+++ b/src/scratch/costume_p.h
@@ -8,6 +8,7 @@ namespace libscratchcpp
 struct CostumePrivate
 {
         CostumePrivate();
+        CostumePrivate(const CostumePrivate &) = delete;
 
         double bitmapResolution = 1;
         int rotationCenterX = 0;
diff --git a/src/scratch/sound.cpp b/src/scratch/sound.cpp
index 0cfe94fd..32b29421 100644
--- a/src/scratch/sound.cpp
+++ b/src/scratch/sound.cpp
@@ -9,7 +9,7 @@ using namespace libscratchcpp;
 /*! Constructs Sound. */
 Sound::Sound(const std::string &name, const std::string &id, const std::string &format) :
     Asset(name, id, format),
-    impl(spimpl::make_impl<SoundPrivate>())
+    impl(spimpl::make_unique_impl<SoundPrivate>())
 {
 }
 
diff --git a/src/scratch/sound_p.h b/src/scratch/sound_p.h
index 12e25903..c2631d26 100644
--- a/src/scratch/sound_p.h
+++ b/src/scratch/sound_p.h
@@ -10,6 +10,7 @@ namespace libscratchcpp
 struct SoundPrivate
 {
         SoundPrivate();
+        SoundPrivate(const SoundPrivate &) = delete;
 
         int rate = 0;
         int sampleCount = 0;

From 702a66b17c6e199654dbad857d7f0df667c85cdf Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Tue, 29 Aug 2023 20:54:01 +0200
Subject: [PATCH 10/56] Add engine property to Target

---
 include/scratchcpp/target.h          |  4 ++++
 src/scratch/target.cpp               | 12 ++++++++++++
 src/scratch/target_p.h               |  2 ++
 test/scratch_classes/CMakeLists.txt  |  2 ++
 test/scratch_classes/target_test.cpp | 11 +++++++++++
 5 files changed, 31 insertions(+)

diff --git a/include/scratchcpp/target.h b/include/scratchcpp/target.h
index 3a9b01a7..7262394b 100644
--- a/include/scratchcpp/target.h
+++ b/include/scratchcpp/target.h
@@ -10,6 +10,7 @@
 namespace libscratchcpp
 {
 
+class IEngine;
 class Variable;
 class List;
 class Block;
@@ -67,6 +68,9 @@ class LIBSCRATCHCPP_EXPORT Target
         int volume() const;
         void setVolume(int newVolume);
 
+        IEngine *engine() const;
+        void setEngine(IEngine *engine);
+
     private:
         spimpl::unique_impl_ptr<TargetPrivate> impl;
 };
diff --git a/src/scratch/target.cpp b/src/scratch/target.cpp
index e0413a1b..5555b403 100644
--- a/src/scratch/target.cpp
+++ b/src/scratch/target.cpp
@@ -289,3 +289,15 @@ void Target::setVolume(int newVolume)
 {
     impl->volume = newVolume;
 }
+
+/*! Returns the engine. */
+IEngine *Target::engine() const
+{
+    return impl->engine;
+}
+
+/*! Sets the engine. */
+void Target::setEngine(IEngine *engine)
+{
+    impl->engine = engine;
+}
diff --git a/src/scratch/target_p.h b/src/scratch/target_p.h
index 1a990b23..4ea13e05 100644
--- a/src/scratch/target_p.h
+++ b/src/scratch/target_p.h
@@ -11,6 +11,7 @@
 namespace libscratchcpp
 {
 
+class IEngine;
 class Variable;
 class List;
 class Block;
@@ -20,6 +21,7 @@ struct TargetPrivate
         TargetPrivate();
         TargetPrivate(const TargetPrivate &) = delete;
 
+        IEngine *engine = nullptr;
         std::string name;
         std::vector<std::shared_ptr<Variable>> variables;
         std::vector<std::shared_ptr<List>> lists;
diff --git a/test/scratch_classes/CMakeLists.txt b/test/scratch_classes/CMakeLists.txt
index 787496d7..49bb075b 100644
--- a/test/scratch_classes/CMakeLists.txt
+++ b/test/scratch_classes/CMakeLists.txt
@@ -63,7 +63,9 @@ add_executable(
 target_link_libraries(
   target_test
   GTest::gtest_main
+  GTest::gmock_main
   scratchcpp
+  scratchcpp_mocks
 )
 
 gtest_discover_tests(target_test)
diff --git a/test/scratch_classes/target_test.cpp b/test/scratch_classes/target_test.cpp
index 4112f12d..ce623368 100644
--- a/test/scratch_classes/target_test.cpp
+++ b/test/scratch_classes/target_test.cpp
@@ -4,6 +4,7 @@
 #include <scratchcpp/block.h>
 #include <scratchcpp/costume.h>
 #include <scratchcpp/sound.h>
+#include <enginemock.h>
 
 #include "../common.h"
 
@@ -194,3 +195,13 @@ TEST(TargetTest, Volume)
     target.setVolume(50);
     ASSERT_EQ(target.volume(), 50);
 }
+
+TEST(TargetTest, Engine)
+{
+    Target target;
+    ASSERT_EQ(target.engine(), nullptr);
+
+    EngineMock engine;
+    target.setEngine(&engine);
+    ASSERT_EQ(target.engine(), &engine);
+}

From 66f8b8e8a32afb52efe657f2c123e8990addd8e0 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Tue, 29 Aug 2023 20:57:02 +0200
Subject: [PATCH 11/56] README: Mark top level reporter blocks as finished

---
 README.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/README.md b/README.md
index 54665b2b..e988dc68 100644
--- a/README.md
+++ b/README.md
@@ -125,7 +125,7 @@ int main(int argc, char **argv) {
 - [ ] Sensing blocks
 - [x] Operator blocks
 - [x] Custom blocks
-- [ ] Top level reporter blocks
+- [x] Top level reporter blocks
 - [x] Broadcasts
 - [x] Variables (monitors are not implemented yet)
 - [x] Lists (monitors are not implemented yet)

From aab0942dcc705ed9cfa36c5dd32b17dee3905adc Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Tue, 29 Aug 2023 21:07:21 +0200
Subject: [PATCH 12/56] Make targets aware of engine

---
 src/engine/internal/engine.cpp |  3 ++-
 test/engine/engine_test.cpp    | 11 +++++++++++
 2 files changed, 13 insertions(+), 1 deletion(-)

diff --git a/src/engine/internal/engine.cpp b/src/engine/internal/engine.cpp
index 2eefcca0..4ed65248 100644
--- a/src/engine/internal/engine.cpp
+++ b/src/engine/internal/engine.cpp
@@ -461,8 +461,9 @@ void Engine::setTargets(const std::vector<std::shared_ptr<Target>> &newTargets)
 {
     m_targets = newTargets;
 
-    // Set engine and target in all blocks
+    // Set engine in targets and engine and target in all blocks
     for (auto target : m_targets) {
+        target->setEngine(this);
         auto blocks = target->blocks();
         for (auto block : blocks) {
             block->setEngine(this);
diff --git a/test/engine/engine_test.cpp b/test/engine/engine_test.cpp
index f9fed87b..2b4ee447 100644
--- a/test/engine/engine_test.cpp
+++ b/test/engine/engine_test.cpp
@@ -255,7 +255,11 @@ TEST(EngineTest, Targets)
     auto t1 = std::make_shared<Target>();
     t1->setName("Sprite1");
     auto t2 = std::make_shared<Target>();
+    auto block1 = std::make_shared<Block>("", "");
+    auto block2 = std::make_shared<Block>("", "");
     t2->setName("Sprite2");
+    t2->addBlock(block1);
+    t2->addBlock(block2);
     auto t3 = std::make_shared<Target>();
     t3->setName("Stage");
     engine.setTargets({ t1, t2, t3 });
@@ -271,4 +275,11 @@ TEST(EngineTest, Targets)
     ASSERT_EQ(engine.findTarget("Sprite1"), 0);
     ASSERT_EQ(engine.findTarget("Sprite2"), 1);
     ASSERT_EQ(engine.findTarget("Stage"), 2);
+
+    ASSERT_EQ(t1->engine(), &engine);
+    ASSERT_EQ(t2->engine(), &engine);
+    ASSERT_EQ(t3->engine(), &engine);
+
+    ASSERT_EQ(block1->engine(), &engine);
+    ASSERT_EQ(block2->engine(), &engine);
 }

From 3ced3d2367b2fd55fb5bdf0187925b210049fbea Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Wed, 30 Aug 2023 10:29:15 +0200
Subject: [PATCH 13/56] Add API for dropdown menus

---
 include/scratchcpp/input.h          |  3 ++
 src/scratch/input.cpp               | 33 +++++++++++++
 test/scratch_classes/input_test.cpp | 74 +++++++++++++++++++++++++++++
 3 files changed, 110 insertions(+)

diff --git a/include/scratchcpp/input.h b/include/scratchcpp/input.h
index 79abb5cb..24d91e42 100644
--- a/include/scratchcpp/input.h
+++ b/include/scratchcpp/input.h
@@ -45,6 +45,9 @@ class LIBSCRATCHCPP_EXPORT Input
         void setValueBlock(std::shared_ptr<Block> block);
         void setValueBlockId(const std::string &id);
 
+        bool pointsToDropdownMenu() const;
+        std::string selectedMenuItem() const;
+
     private:
         spimpl::unique_impl_ptr<InputPrivate> impl;
 };
diff --git a/src/scratch/input.cpp b/src/scratch/input.cpp
index 3563773b..2e2e16c8 100644
--- a/src/scratch/input.cpp
+++ b/src/scratch/input.cpp
@@ -2,6 +2,7 @@
 
 #include <iostream>
 #include <scratchcpp/input.h>
+#include <scratchcpp/field.h>
 #include <scratchcpp/variable.h>
 #include <scratchcpp/block.h>
 
@@ -94,3 +95,35 @@ void Input::setValueBlockId(const std::string &id)
     impl->primaryValue.setValueBlock(nullptr);
     impl->primaryValue.setValueBlockId(id);
 }
+
+/*!
+ * Returns true if the input points to a dropdown menu.\n
+ * (if type() == Type::Shadow and valueBlock() points to a block with a single field which does not point to an entity)
+ */
+bool Input::pointsToDropdownMenu() const
+{
+    auto block = valueBlock();
+
+    if ((impl->type != Type::Shadow) || !block)
+        return false;
+
+    const auto &fields = block->fields();
+
+    if (fields.size() != 1)
+        return false;
+
+    auto field = fields[0];
+    return (field && !field->valuePtr() && (field->valueId() == ""));
+}
+
+/*!
+ * Returns the selected item in the dropdown menu.\n
+ * Works only pointsToDropdownMenu() is true.
+ */
+std::string Input::selectedMenuItem() const
+{
+    if (!pointsToDropdownMenu())
+        return "";
+
+    return valueBlock()->fieldAt(0)->value().toString();
+}
diff --git a/test/scratch_classes/input_test.cpp b/test/scratch_classes/input_test.cpp
index 5c9d2b4b..ac8ca86f 100644
--- a/test/scratch_classes/input_test.cpp
+++ b/test/scratch_classes/input_test.cpp
@@ -1,6 +1,8 @@
 #include <scratchcpp/input.h>
 #include <scratchcpp/inputvalue.h>
+#include <scratchcpp/field.h>
 #include <scratchcpp/block.h>
+#include <scratchcpp/variable.h>
 
 #include "../common.h"
 
@@ -64,3 +66,75 @@ TEST(InputTest, ValueBlock)
     ASSERT_EQ(input.valueBlock(), nullptr);
     ASSERT_EQ(input.valueBlockId(), "");
 }
+
+TEST(InputTest, SelectedMenuItem)
+{
+    // Test with Shadow type
+    Input input1("", Input::Type::Shadow);
+    ASSERT_FALSE(input1.pointsToDropdownMenu());
+    ASSERT_TRUE(input1.selectedMenuItem().empty());
+
+    auto block1 = std::make_shared<Block>("abc", "");
+    input1.setValueBlock(block1);
+    ASSERT_FALSE(input1.pointsToDropdownMenu());
+    ASSERT_TRUE(input1.selectedMenuItem().empty());
+
+    auto field1 = std::make_shared<Field>("OPTION1", "");
+    block1->addField(field1);
+    ASSERT_TRUE(input1.pointsToDropdownMenu());
+    ASSERT_TRUE(input1.selectedMenuItem().empty());
+
+    auto field2 = std::make_shared<Field>("OPTION2", "something");
+    block1->addField(field2);
+    ASSERT_FALSE(input1.pointsToDropdownMenu());
+    ASSERT_TRUE(input1.selectedMenuItem().empty());
+
+    auto block2 = std::make_shared<Block>("def", "");
+    input1.setValueBlock(block2);
+
+    field1 = std::make_shared<Field>("OPTION1", "something");
+    block2->addField(field1);
+    ASSERT_TRUE(input1.pointsToDropdownMenu());
+    ASSERT_EQ(input1.selectedMenuItem(), "something");
+
+    auto variable = std::make_shared<Variable>("", "");
+    field1->setValuePtr(variable);
+    ASSERT_FALSE(input1.pointsToDropdownMenu());
+    ASSERT_TRUE(input1.selectedMenuItem().empty());
+
+    field1->setValuePtr(nullptr);
+    ASSERT_TRUE(input1.pointsToDropdownMenu());
+    ASSERT_EQ(input1.selectedMenuItem(), "something");
+
+    field2 = std::make_shared<Field>("OPTION2", "hello");
+    block2->addField(field2);
+    ASSERT_FALSE(input1.pointsToDropdownMenu());
+    ASSERT_TRUE(input1.selectedMenuItem().empty());
+
+    // Test with NoShadow type
+    Input input2("", Input::Type::NoShadow);
+    ASSERT_FALSE(input2.pointsToDropdownMenu());
+    ASSERT_TRUE(input2.selectedMenuItem().empty());
+
+    auto block3 = std::make_shared<Block>("ghi", "");
+    input2.setValueBlock(block3);
+    ASSERT_FALSE(input2.pointsToDropdownMenu());
+    ASSERT_TRUE(input2.selectedMenuItem().empty());
+
+    block3->addField(field1);
+    ASSERT_FALSE(input2.pointsToDropdownMenu());
+    ASSERT_TRUE(input2.selectedMenuItem().empty());
+
+    // Test with ObscuredShadow type
+    Input input3("", Input::Type::ObscuredShadow);
+    ASSERT_FALSE(input3.pointsToDropdownMenu());
+    ASSERT_TRUE(input3.selectedMenuItem().empty());
+
+    input3.setValueBlock(block3);
+    ASSERT_FALSE(input3.pointsToDropdownMenu());
+    ASSERT_TRUE(input3.selectedMenuItem().empty());
+
+    block3->addField(field1);
+    ASSERT_FALSE(input3.pointsToDropdownMenu());
+    ASSERT_TRUE(input3.selectedMenuItem().empty());
+}

From 25534cc02070148b6515141d64699be60747eb19 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Wed, 30 Aug 2023 10:53:44 +0200
Subject: [PATCH 14/56] Compile selected items in dropdown menus

---
 src/engine/compiler.cpp         | 21 +++++++++++++--------
 src/engine/compiler_p.cpp       | 10 ++++++++++
 src/engine/compiler_p.h         |  3 +++
 test/compiler/compiler_test.cpp | 25 +++++++++++++++++++++++--
 4 files changed, 49 insertions(+), 10 deletions(-)

diff --git a/src/engine/compiler.cpp b/src/engine/compiler.cpp
index 78964e5d..037fb9a9 100644
--- a/src/engine/compiler.cpp
+++ b/src/engine/compiler.cpp
@@ -98,8 +98,14 @@ const std::vector<InputValue *> &Compiler::constInputValues() const
 std::vector<Value> Compiler::constValues() const
 {
     std::vector<Value> ret;
-    for (auto value : impl->constValues)
-        ret.push_back(value->value());
+    for (auto value : impl->constValues) {
+        const auto &menuInfo = impl->constValueMenuInfo.at(value);
+
+        if (menuInfo.first)
+            ret.push_back(menuInfo.second);
+        else
+            ret.push_back(value->value());
+    }
     return ret;
 }
 
@@ -139,7 +145,10 @@ void Compiler::addInput(Input *input)
     }
     switch (input->type()) {
         case Input::Type::Shadow:
-            addInstruction(OP_CONST, { constIndex(input->primaryValue()) });
+            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: {
@@ -275,11 +284,7 @@ unsigned int Compiler::listIndex(std::shared_ptr<Entity> listEntity)
 /*! Returns the index of the given constant input value. */
 unsigned int Compiler::constIndex(InputValue *value)
 {
-    auto it = std::find(impl->constValues.begin(), impl->constValues.end(), value);
-    if (it != impl->constValues.end())
-        return it - impl->constValues.begin();
-    impl->constValues.push_back(value);
-    return impl->constValues.size() - 1;
+    return impl->constIndex(value);
 }
 
 /*! Returns the index of the procedure code of the given block. */
diff --git a/src/engine/compiler_p.cpp b/src/engine/compiler_p.cpp
index 9d938edf..7df13726 100644
--- a/src/engine/compiler_p.cpp
+++ b/src/engine/compiler_p.cpp
@@ -20,6 +20,16 @@ void CompilerPrivate::addInstruction(vm::Opcode opcode, std::initializer_list<un
         bytecode.push_back(arg);
 }
 
+unsigned int CompilerPrivate::constIndex(InputValue *value, bool pointsToDropdownMenu, const std::string &selectedMenuItem)
+{
+    auto it = std::find(constValues.begin(), constValues.end(), value);
+    if (it != constValues.end())
+        return it - constValues.begin();
+    constValues.push_back(value);
+    constValueMenuInfo[value] = { pointsToDropdownMenu, selectedMenuItem };
+    return constValues.size() - 1;
+}
+
 void CompilerPrivate::substackEnd()
 {
     auto parent = substackTree.back();
diff --git a/src/engine/compiler_p.h b/src/engine/compiler_p.h
index 47d37a50..819b074a 100644
--- a/src/engine/compiler_p.h
+++ b/src/engine/compiler_p.h
@@ -13,6 +13,8 @@ struct CompilerPrivate
 
         void addInstruction(vm::Opcode opcode, std::initializer_list<unsigned int> args = {});
 
+        unsigned int constIndex(InputValue *value, bool pointsToDropdownMenu = false, const std::string &selectedMenuItem = "");
+
         void substackEnd();
 
         IEngine *engine;
@@ -23,6 +25,7 @@ struct CompilerPrivate
 
         std::vector<unsigned int> bytecode;
         std::vector<InputValue *> constValues;
+        std::unordered_map<InputValue *, std::pair<bool, std::string>> constValueMenuInfo; // input value, <whether the input points to a dropdown menu, selected menu item>
         std::vector<Variable *> variables;
         std::vector<List *> lists;
         std::vector<std::string> procedures;
diff --git a/test/compiler/compiler_test.cpp b/test/compiler/compiler_test.cpp
index 669e40e0..f4622c50 100644
--- a/test/compiler/compiler_test.cpp
+++ b/test/compiler/compiler_test.cpp
@@ -329,11 +329,32 @@ TEST_F(CompilerTest, ResolveInput)
     engine.registerSection(m_section);
 
     auto block = std::make_shared<Block>("a", "test_block1");
-    auto reporter = std::make_shared<Block>("a", "test_reporter");
     auto input = std::make_shared<Input>("INPUT1", Input::Type::Shadow);
     input->setPrimaryValue("test");
     input->setInputId(TestBlockSection::INPUT1);
-    input->setValueBlock(reporter);
+    block->addInput(input);
+    block->updateInputMap();
+    block->setCompileFunction(&TestBlockSection::compileTestBlock1);
+
+    compiler.compile(block);
+
+    ASSERT_EQ(compiler.bytecode(), std::vector<unsigned int>({ vm::OP_START, vm::OP_CONST, 0, vm::OP_PRINT, vm::OP_HALT }));
+    ASSERT_EQ(compiler.constValues(), std::vector<Value>({ "test" }));
+}
+
+TEST_F(CompilerTest, ResolveDropdownMenuInput)
+{
+    INIT_COMPILER(engine, compiler);
+
+    engine.registerSection(m_section);
+
+    auto block = std::make_shared<Block>("a", "test_block1");
+    auto input = std::make_shared<Input>("INPUT1", Input::Type::Shadow);
+    auto menu = std::make_shared<Block>("a", "test_menu");
+    auto optionField = std::make_shared<Field>("OPTION", "test");
+    input->setInputId(TestBlockSection::INPUT1);
+    input->setValueBlock(menu);
+    menu->addField(optionField);
     block->addInput(input);
     block->updateInputMap();
     block->setCompileFunction(&TestBlockSection::compileTestBlock1);

From 5ba52f6a097ec18585c6c4e7093de9c784c67c77 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Wed, 30 Aug 2023 12:11:33 +0200
Subject: [PATCH 15/56] Add addConstValue method to Compiler

---
 include/scratchcpp/compiler.h   |  1 +
 src/engine/compiler.cpp         |  9 ++++++++-
 src/engine/compiler_p.h         |  3 +++
 test/compiler/compiler_test.cpp | 21 +++++++++++++++++++++
 4 files changed, 33 insertions(+), 1 deletion(-)

diff --git a/include/scratchcpp/compiler.h b/include/scratchcpp/compiler.h
index 36515007..f316ac25 100644
--- a/include/scratchcpp/compiler.h
+++ b/include/scratchcpp/compiler.h
@@ -56,6 +56,7 @@ class LIBSCRATCHCPP_EXPORT Compiler
         void addInstruction(vm::Opcode opcode, const std::initializer_list<unsigned int> &args = {});
         void addInput(Input *input);
         void addInput(int id);
+        void addConstValue(const Value &value);
         void addFunctionCall(BlockFunc f);
         void addProcedureArg(const std::string &procCode, const std::string &argName);
         void moveToSubstack(std::shared_ptr<Block> substack1, std::shared_ptr<Block> substack2, SubstackType type);
diff --git a/src/engine/compiler.cpp b/src/engine/compiler.cpp
index 037fb9a9..f3901e9e 100644
--- a/src/engine/compiler.cpp
+++ b/src/engine/compiler.cpp
@@ -3,7 +3,6 @@
 #include <scratchcpp/compiler.h>
 #include <scratchcpp/iengine.h>
 #include <scratchcpp/input.h>
-#include <scratchcpp/inputvalue.h>
 #include <scratchcpp/block.h>
 #include <scratchcpp/variable.h>
 #include <scratchcpp/list.h>
@@ -189,6 +188,14 @@ void Compiler::addInput(int id)
     addInput(input(id));
 }
 
+/*! Adds a constant value and an instruction to load it. Useful if you don't have an input for the addInput() method. */
+void libscratchcpp::Compiler::addConstValue(const Value &value)
+{
+    impl->customConstValues.push_back(std::make_unique<InputValue>());
+    impl->customConstValues.back()->setValue(value);
+    addInstruction(OP_CONST, { impl->constIndex(impl->customConstValues.back().get()) });
+}
+
 /*! Adds a function call to the bytecode (the OP_EXEC instruction). */
 void Compiler::addFunctionCall(BlockFunc f)
 {
diff --git a/src/engine/compiler_p.h b/src/engine/compiler_p.h
index 819b074a..2b99935b 100644
--- a/src/engine/compiler_p.h
+++ b/src/engine/compiler_p.h
@@ -3,6 +3,7 @@
 #pragma once
 
 #include <scratchcpp/compiler.h>
+#include <scratchcpp/inputvalue.h>
 
 namespace libscratchcpp
 {
@@ -10,6 +11,7 @@ namespace libscratchcpp
 struct CompilerPrivate
 {
         CompilerPrivate(IEngine *engine);
+        CompilerPrivate(const CompilerPrivate &) = delete;
 
         void addInstruction(vm::Opcode opcode, std::initializer_list<unsigned int> args = {});
 
@@ -25,6 +27,7 @@ struct CompilerPrivate
 
         std::vector<unsigned int> bytecode;
         std::vector<InputValue *> constValues;
+        std::vector<std::unique_ptr<InputValue>> customConstValues;
         std::unordered_map<InputValue *, std::pair<bool, std::string>> constValueMenuInfo; // input value, <whether the input points to a dropdown menu, selected menu item>
         std::vector<Variable *> variables;
         std::vector<List *> lists;
diff --git a/test/compiler/compiler_test.cpp b/test/compiler/compiler_test.cpp
index f4622c50..572b5069 100644
--- a/test/compiler/compiler_test.cpp
+++ b/test/compiler/compiler_test.cpp
@@ -365,6 +365,27 @@ TEST_F(CompilerTest, ResolveDropdownMenuInput)
     ASSERT_EQ(compiler.constValues(), std::vector<Value>({ "test" }));
 }
 
+TEST_F(CompilerTest, AddConstValue)
+{
+    INIT_COMPILER(engine, compiler);
+    compiler.addInstruction(vm::OP_START);
+
+    compiler.addConstValue(50);
+    compiler.addInstruction(vm::OP_PRINT);
+    compiler.addConstValue(50);
+    compiler.addInstruction(vm::OP_PRINT);
+    compiler.addConstValue("hello");
+    compiler.addInstruction(vm::OP_PRINT);
+    compiler.addConstValue("world");
+    compiler.addInstruction(vm::OP_PRINT);
+
+    compiler.addInstruction(vm::OP_HALT);
+    ASSERT_EQ(
+        compiler.bytecode(),
+        std::vector<unsigned int>({ vm::OP_START, vm::OP_CONST, 0, vm::OP_PRINT, vm::OP_CONST, 1, vm::OP_PRINT, vm::OP_CONST, 2, vm::OP_PRINT, vm::OP_CONST, 3, vm::OP_PRINT, vm::OP_HALT }));
+    ASSERT_EQ(compiler.constValues(), std::vector<Value>({ 50, 50, "hello", "world" }));
+}
+
 TEST_F(CompilerTest, ResolveField)
 {
     INIT_COMPILER(engine, compiler);

From 67ad17689f26295ef2e39e0ebd4f3e649fc408f1 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Wed, 30 Aug 2023 12:17:59 +0200
Subject: [PATCH 16/56] Use Compiler::addConstValue() in broadcast blocks

Project data shouldn't be altered when compiling or running projects.
---
 src/blocks/eventblocks.cpp        | 16 ++++++++++------
 test/blocks/event_blocks_test.cpp |  5 ++---
 2 files changed, 12 insertions(+), 9 deletions(-)

diff --git a/src/blocks/eventblocks.cpp b/src/blocks/eventblocks.cpp
index 3bc65702..f06192f2 100644
--- a/src/blocks/eventblocks.cpp
+++ b/src/blocks/eventblocks.cpp
@@ -34,24 +34,28 @@ void EventBlocks::registerBlocks(IEngine *engine)
 void EventBlocks::compileBroadcast(Compiler *compiler)
 {
     auto input = compiler->input(BROADCAST_INPUT);
-    compiler->addInput(input);
+
     if (input->type() != Input::Type::ObscuredShadow) {
-        input->primaryValue()->setValue(compiler->engine()->findBroadcast(input->primaryValue()->value().toString()));
+        compiler->addConstValue(compiler->engine()->findBroadcast(input->primaryValue()->value().toString()));
         compiler->addFunctionCall(&broadcastByIndex);
-    } else
+    } else {
+        compiler->addInput(input);
         compiler->addFunctionCall(&broadcast);
+    }
 }
 
 void EventBlocks::compileBroadcastAndWait(Compiler *compiler)
 {
     auto input = compiler->input(BROADCAST_INPUT);
-    compiler->addInput(input);
+
     if (input->type() != Input::Type::ObscuredShadow) {
-        input->primaryValue()->setValue(compiler->engine()->findBroadcast(input->primaryValue()->value().toString()));
+        int index = compiler->engine()->findBroadcast(input->primaryValue()->value().toString());
+        compiler->addConstValue(index);
         compiler->addFunctionCall(&broadcastByIndexAndWait);
-        compiler->addInput(input);
+        compiler->addConstValue(index);
         compiler->addFunctionCall(&checkBroadcastByIndex);
     } else {
+        compiler->addInput(input);
         compiler->addFunctionCall(&broadcastAndWait);
         compiler->addInput(input);
         compiler->addFunctionCall(&checkBroadcast);
diff --git a/test/blocks/event_blocks_test.cpp b/test/blocks/event_blocks_test.cpp
index 0c1930f2..20ed514c 100644
--- a/test/blocks/event_blocks_test.cpp
+++ b/test/blocks/event_blocks_test.cpp
@@ -180,9 +180,8 @@ TEST_F(EventBlocksTest, BroadcastAndWait)
     ASSERT_EQ(
         compiler.bytecode(),
         std::vector<unsigned int>(
-            { vm::OP_START, vm::OP_CONST, 0, vm::OP_EXEC, 0, vm::OP_CONST, 0, vm::OP_EXEC, 1, vm::OP_NULL, vm::OP_NOT, vm::OP_EXEC, 2, vm::OP_NULL, vm::OP_NOT, vm::OP_EXEC, 3, vm::OP_HALT }));
-    ASSERT_EQ(compiler.constValues().size(), 1);
-    ASSERT_EQ(compiler.constValues()[0].toDouble(), 0);
+            { vm::OP_START, vm::OP_CONST, 0, vm::OP_EXEC, 0, vm::OP_CONST, 1, vm::OP_EXEC, 1, vm::OP_NULL, vm::OP_NOT, vm::OP_EXEC, 2, vm::OP_NULL, vm::OP_NOT, vm::OP_EXEC, 3, vm::OP_HALT }));
+    ASSERT_EQ(compiler.constValues(), std::vector<Value>({ 0, 0 }));
     ASSERT_TRUE(compiler.variables().empty());
     ASSERT_TRUE(compiler.lists().empty());
 }

From f3e0aab4e55567ba384dd36dc2884b94472b6894 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Thu, 31 Aug 2023 18:15:47 +0200
Subject: [PATCH 17/56] Add a way to use a custom target in Script::start()

---
 include/scratchcpp/script.h |  1 +
 src/engine/script.cpp       |  8 +++++++-
 test/script/script_test.cpp | 11 +++++++++++
 3 files changed, 19 insertions(+), 1 deletion(-)

diff --git a/include/scratchcpp/script.h b/include/scratchcpp/script.h
index 8090d603..0e01a80c 100644
--- a/include/scratchcpp/script.h
+++ b/include/scratchcpp/script.h
@@ -36,6 +36,7 @@ class LIBSCRATCHCPP_EXPORT Script
         void setLists(const std::vector<List *> &lists);
 
         std::shared_ptr<VirtualMachine> start();
+        std::shared_ptr<VirtualMachine> start(Target *target);
 
     private:
         spimpl::unique_impl_ptr<ScriptPrivate> impl;
diff --git a/src/engine/script.cpp b/src/engine/script.cpp
index a6739fe9..729b363f 100644
--- a/src/engine/script.cpp
+++ b/src/engine/script.cpp
@@ -35,7 +35,13 @@ void Script::setBytecode(const std::vector<unsigned int> &code)
 /*! Starts the script (creates a virtual machine). */
 std::shared_ptr<VirtualMachine> Script::start()
 {
-    auto vm = std::make_shared<VirtualMachine>(impl->target, impl->engine, this);
+    return start(impl->target);
+}
+
+/*! Starts the script (creates a virtual machine). */
+std::shared_ptr<VirtualMachine> Script::start(Target *target)
+{
+    auto vm = std::make_shared<VirtualMachine>(target, impl->engine, this);
     vm->setBytecode(impl->bytecode);
     vm->setProcedures(impl->procedures);
     vm->setFunctions(impl->functions);
diff --git a/test/script/script_test.cpp b/test/script/script_test.cpp
index 36ac6cc1..d0d9ad6a 100644
--- a/test/script/script_test.cpp
+++ b/test/script/script_test.cpp
@@ -81,4 +81,15 @@ TEST_F(ScriptTest, Start)
     ASSERT_EQ(vm->constValues()[0].toString(), constValues[0].toString());
     ASSERT_EQ(vm->variables()[0], variables[0]);
     ASSERT_EQ(vm->lists()[0], lists[0]);
+
+    Target target;
+    vm = script3.start(&target);
+    ASSERT_TRUE(vm);
+    ASSERT_EQ(vm->target(), &target);
+    ASSERT_EQ(vm->bytecode()[0], bytecode[0]);
+    ASSERT_EQ(vm->procedures()[0], procedures[0]);
+    ASSERT_EQ(vm->functions()[0], functions[0]);
+    ASSERT_EQ(vm->constValues()[0].toString(), constValues[0].toString());
+    ASSERT_EQ(vm->variables()[0], variables[0]);
+    ASSERT_EQ(vm->lists()[0], lists[0]);
 }

From 76ab1172fad20692a024e2392f14442dbf62663b Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Thu, 31 Aug 2023 18:42:19 +0200
Subject: [PATCH 18/56] Sprite: Add clone info methods

---
 include/scratchcpp/sprite.h |  6 ++++++
 src/scratch/sprite.cpp      | 27 +++++++++++++++++++++++++++
 src/scratch/sprite_p.cpp    | 13 +++++++++++++
 src/scratch/sprite_p.h      |  5 +++++
 4 files changed, 51 insertions(+)

diff --git a/include/scratchcpp/sprite.h b/include/scratchcpp/sprite.h
index d9ede3d5..952040d2 100644
--- a/include/scratchcpp/sprite.h
+++ b/include/scratchcpp/sprite.h
@@ -22,9 +22,15 @@ class LIBSCRATCHCPP_EXPORT Sprite : public Target
         };
 
         Sprite();
+        ~Sprite();
 
         void setInterface(ISpriteHandler *newInterface);
 
+        bool isClone() const;
+
+        Sprite *cloneRoot() const;
+        Sprite *cloneParent() const;
+
         bool visible() const;
         void setVisible(bool newVisible);
 
diff --git a/src/scratch/sprite.cpp b/src/scratch/sprite.cpp
index 206db9c6..7ae1b2db 100644
--- a/src/scratch/sprite.cpp
+++ b/src/scratch/sprite.cpp
@@ -16,6 +16,15 @@ Sprite::Sprite() :
 {
 }
 
+/*! Destroys the Sprite object. */
+Sprite::~Sprite()
+{
+    if (isClone()) {
+        assert(impl->cloneParent);
+        impl->cloneParent->impl->removeClone(this);
+    }
+}
+
 /*! Sets the sprite interface. */
 void Sprite::setInterface(ISpriteHandler *newInterface)
 {
@@ -24,6 +33,24 @@ void Sprite::setInterface(ISpriteHandler *newInterface)
     impl->iface->onSpriteChanged(this);
 }
 
+/*! Returns true if this is a clone. */
+bool Sprite::isClone() const
+{
+    return (impl->cloneParent != nullptr);
+}
+
+/*! Returns the sprite this clone was created from, or nullptr if this isn't a clone. */
+Sprite *Sprite::cloneRoot() const
+{
+    return impl->cloneRoot;
+}
+
+/*! Returns the sprite or clone this clone was created from, or nullptr if this isn't a clone. */
+Sprite *Sprite::cloneParent() const
+{
+    return impl->cloneParent;
+}
+
 /*! Returns true if the sprite is visible. */
 bool Sprite::visible() const
 {
diff --git a/src/scratch/sprite_p.cpp b/src/scratch/sprite_p.cpp
index 81333fbc..22267c42 100644
--- a/src/scratch/sprite_p.cpp
+++ b/src/scratch/sprite_p.cpp
@@ -10,6 +10,19 @@ SpritePrivate::SpritePrivate()
 {
 }
 
+void SpritePrivate::removeClone(Sprite *clone)
+{
+    int index = 0;
+    for (const auto &child : childClones) {
+        if (child.get() == clone) {
+            childClones.erase(childClones.begin() + index);
+            return;
+        }
+
+        index++;
+    }
+}
+
 void SpritePrivate::setCostumeData(const char *data)
 {
     if (iface)
diff --git a/src/scratch/sprite_p.h b/src/scratch/sprite_p.h
index 2656f228..84ca01ea 100644
--- a/src/scratch/sprite_p.h
+++ b/src/scratch/sprite_p.h
@@ -12,9 +12,14 @@ struct SpritePrivate
         SpritePrivate();
         SpritePrivate(const SpritePrivate &) = delete;
 
+        void removeClone(Sprite *clone);
+
         void setCostumeData(const char *data);
 
         ISpriteHandler *iface = nullptr;
+        Sprite *cloneRoot = nullptr;
+        Sprite *cloneParent = nullptr;
+        std::vector<std::shared_ptr<Sprite>> childClones;
         bool visible = true;
         double x = 0;
         double y = 0;

From af49e8be4f2ccb2697651cf78b4873b6b1caf1e5 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Thu, 31 Aug 2023 18:45:31 +0200
Subject: [PATCH 19/56] Engine: Add clone API

---
 include/scratchcpp/iengine.h   |  7 +++++
 src/engine/internal/engine.cpp | 49 ++++++++++++++++++++++++++++++++++
 src/engine/internal/engine.h   |  4 +++
 test/mocks/enginemock.h        |  3 +++
 4 files changed, 63 insertions(+)

diff --git a/include/scratchcpp/iengine.h b/include/scratchcpp/iengine.h
index 0d9d8fca..94d6648a 100644
--- a/include/scratchcpp/iengine.h
+++ b/include/scratchcpp/iengine.h
@@ -15,6 +15,7 @@ class IBlockSection;
 class Broadcast;
 class Block;
 class Target;
+class Sprite;
 class Script;
 
 /*!
@@ -70,6 +71,9 @@ class LIBSCRATCHCPP_EXPORT IEngine
          */
         virtual void stopTarget(Target *target, VirtualMachine *exceptScript) = 0;
 
+        /*! Calls the "when I start as a clone" blocks of the given sprite. */
+        virtual void initClone(Sprite *clone) = 0;
+
         /*!
          * Runs the event loop and calls "when green flag clicked" blocks.
          * \note This function returns when all scripts finish.\n
@@ -157,6 +161,9 @@ class LIBSCRATCHCPP_EXPORT IEngine
         /*! Registers the broadcast script. */
         virtual void addBroadcastScript(std::shared_ptr<Block> whenReceivedBlock, std::shared_ptr<Broadcast> broadcast) = 0;
 
+        /* Registers the given "when I start as clone" script. */
+        virtual void addCloneInitScript(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 4ed65248..6257c5a9 100644
--- a/src/engine/internal/engine.cpp
+++ b/src/engine/internal/engine.cpp
@@ -3,6 +3,7 @@
 #include <scratchcpp/scratchconfiguration.h>
 #include <scratchcpp/iblocksection.h>
 #include <scratchcpp/script.h>
+#include <scratchcpp/sprite.h>
 #include <scratchcpp/broadcast.h>
 #include <scratchcpp/compiler.h>
 #include <scratchcpp/input.h>
@@ -258,6 +259,37 @@ void Engine::stopTarget(Target *target, VirtualMachine *exceptScript)
         stopScript(script);
 }
 
+void Engine::initClone(Sprite *clone)
+{
+    if (!clone)
+        return;
+
+    Sprite *source = clone->cloneParent();
+    Target *root = clone->cloneRoot();
+    assert(source);
+    assert(root);
+
+    if (!source || !root)
+        return;
+
+    auto it = m_cloneInitScriptsMap.find(root);
+
+    if (it != m_cloneInitScriptsMap.cend()) {
+        const auto &scripts = it->second;
+
+#ifndef NDEBUG
+        // Since we're initializing the clone, it shouldn't have any running scripts
+        for (const auto script : m_runningScripts)
+            assert(script->target() != clone);
+#endif
+
+        for (auto script : scripts) {
+            auto vm = script->start(clone);
+            m_runningScripts.push_back(vm);
+        }
+    }
+}
+
 void Engine::run()
 {
     auto frameDuration = std::chrono::milliseconds(33);
@@ -442,6 +474,7 @@ void Engine::addBroadcastScript(std::shared_ptr<Block> whenReceivedBlock, std::s
     auto id = findBroadcast(broadcast->name());
     if (m_broadcastMap.count(id) == 1) {
         std::vector<Script *> &scripts = m_broadcastMap[id];
+        // TODO: Do not allow adding existing scripts
         scripts.push_back(m_scripts[whenReceivedBlock].get());
     } else {
         m_broadcastMap[id] = { m_scripts[whenReceivedBlock].get() };
@@ -452,6 +485,22 @@ void Engine::addBroadcastScript(std::shared_ptr<Block> whenReceivedBlock, std::s
     }
 }
 
+void Engine::addCloneInitScript(std::shared_ptr<Block> hatBlock)
+{
+    Target *target = hatBlock->target();
+    Script *script = m_scripts[hatBlock].get();
+    auto it = m_cloneInitScriptsMap.find(target);
+
+    if (it == m_cloneInitScriptsMap.cend())
+        m_cloneInitScriptsMap[target] = { m_scripts[hatBlock].get() };
+    else {
+        auto &scripts = it->second;
+
+        if (std::find(scripts.begin(), scripts.end(), script) == scripts.cend())
+            scripts.push_back(script);
+    }
+}
+
 const std::vector<std::shared_ptr<Target>> &Engine::targets() const
 {
     return m_targets;
diff --git a/src/engine/internal/engine.h b/src/engine/internal/engine.h
index 6dd5f316..b0e1640c 100644
--- a/src/engine/internal/engine.h
+++ b/src/engine/internal/engine.h
@@ -32,6 +32,7 @@ class Engine : public IEngine
         void broadcast(unsigned int index, VirtualMachine *sourceScript, bool wait = false) override;
         void stopScript(VirtualMachine *vm) override;
         void stopTarget(Target *target, VirtualMachine *exceptScript) override;
+        void initClone(libscratchcpp::Sprite *clone) override;
         void run() override;
 
         bool broadcastRunning(unsigned int index, VirtualMachine *sourceScript) override;
@@ -60,6 +61,8 @@ class Engine : public IEngine
 
         void addBroadcastScript(std::shared_ptr<Block> whenReceivedBlock, std::shared_ptr<Broadcast> broadcast) override;
 
+        void addCloneInitScript(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;
         Target *targetAt(int index) const override;
@@ -86,6 +89,7 @@ class Engine : public IEngine
         std::vector<std::shared_ptr<Broadcast>> m_broadcasts;
         std::unordered_map<unsigned int, std::vector<Script *>> m_broadcastMap;
         std::unordered_map<unsigned int, std::vector<std::pair<VirtualMachine *, VirtualMachine *>>> m_runningBroadcastMap; // source script, "when received" script
+        std::unordered_map<Target *, std::vector<Script *>> m_cloneInitScriptsMap;                                          // target (no clones), "when I start as a clone" scripts
         std::vector<std::string> m_extensions;
         std::vector<std::shared_ptr<VirtualMachine>> m_runningScripts;
         std::vector<VirtualMachine *> m_scriptsToRemove;
diff --git a/test/mocks/enginemock.h b/test/mocks/enginemock.h
index 7d479689..0632cd8d 100644
--- a/test/mocks/enginemock.h
+++ b/test/mocks/enginemock.h
@@ -21,6 +21,7 @@ class EngineMock : public IEngine
         MOCK_METHOD(void, broadcast, (unsigned int, VirtualMachine *, bool), (override));
         MOCK_METHOD(void, stopScript, (VirtualMachine *), (override));
         MOCK_METHOD(void, stopTarget, (Target *, VirtualMachine *), (override));
+        MOCK_METHOD(void, initClone, (Sprite *), (override));
         MOCK_METHOD(void, run, (), (override));
 
         MOCK_METHOD(bool, broadcastRunning, (unsigned int, VirtualMachine *), (override));
@@ -48,6 +49,8 @@ class EngineMock : public IEngine
 
         MOCK_METHOD(void, addBroadcastScript, (std::shared_ptr<Block>, std::shared_ptr<Broadcast>), (override));
 
+        MOCK_METHOD(void, addCloneInitScript, (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));
         MOCK_METHOD(Target *, targetAt, (int), (const, override));

From d7645d53e26b610cd23addd250305be4f5d2706f Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Fri, 1 Sep 2023 10:15:44 +0200
Subject: [PATCH 20/56] Add clone method to Sprite

---
 include/scratchcpp/sprite.h          |   2 +
 src/scratch/sprite.cpp               |  60 +++++++++++++-
 test/scratch_classes/CMakeLists.txt  |   2 +
 test/scratch_classes/sprite_test.cpp | 113 +++++++++++++++++++++++++++
 4 files changed, 176 insertions(+), 1 deletion(-)

diff --git a/include/scratchcpp/sprite.h b/include/scratchcpp/sprite.h
index 952040d2..356e0799 100644
--- a/include/scratchcpp/sprite.h
+++ b/include/scratchcpp/sprite.h
@@ -26,6 +26,8 @@ class LIBSCRATCHCPP_EXPORT Sprite : public Target
 
         void setInterface(ISpriteHandler *newInterface);
 
+        std::shared_ptr<Sprite> clone();
+
         bool isClone() const;
 
         Sprite *cloneRoot() const;
diff --git a/src/scratch/sprite.cpp b/src/scratch/sprite.cpp
index 7ae1b2db..5ffaa657 100644
--- a/src/scratch/sprite.cpp
+++ b/src/scratch/sprite.cpp
@@ -2,7 +2,9 @@
 
 #include <scratchcpp/sprite.h>
 #include <scratchcpp/ispritehandler.h>
-#include <string>
+#include <scratchcpp/iengine.h>
+#include <scratchcpp/variable.h>
+#include <scratchcpp/list.h>
 #include <cassert>
 
 #include "sprite_p.h"
@@ -33,6 +35,62 @@ void Sprite::setInterface(ISpriteHandler *newInterface)
     impl->iface->onSpriteChanged(this);
 }
 
+/*! Creates a clone of the sprite. */
+std::shared_ptr<Sprite> Sprite::clone()
+{
+    IEngine *eng = engine();
+
+    if (eng) {
+        std::shared_ptr<Sprite> clone = std::make_shared<Sprite>();
+
+        if (impl->cloneRoot == nullptr)
+            clone->impl->cloneRoot = this;
+        else
+            clone->impl->cloneRoot = impl->cloneRoot;
+
+        clone->impl->cloneParent = this;
+        impl->childClones.push_back(clone);
+
+        // Copy data
+        clone->setName(name());
+
+        const auto &vars = variables();
+
+        for (auto var : vars)
+            clone->addVariable(std::make_shared<Variable>(var->id(), var->name(), var->value()));
+
+        const auto &l = lists();
+
+        for (auto list : l) {
+            auto newList = std::make_shared<List>(list->id(), list->name());
+            clone->addList(newList);
+
+            for (const Value &item : *list)
+                newList->push_back(item);
+        }
+
+        clone->setCurrentCostume(currentCostume());
+        clone->setLayerOrder(layerOrder());
+        clone->setVolume(volume());
+        clone->setEngine(engine());
+
+        clone->impl->visible = impl->visible;
+        clone->impl->x = impl->x;
+        clone->impl->y = impl->y;
+        clone->impl->size = impl->size;
+        clone->impl->direction = impl->direction;
+        clone->impl->draggable = impl->draggable;
+        clone->impl->rotationStyle = impl->rotationStyle;
+
+        // Call "when I start as clone" scripts
+        eng->initClone(clone.get());
+
+        return clone;
+    }
+
+    return nullptr;
+}
+
 /*! Returns true if this is a clone. */
 bool Sprite::isClone() const
 {
diff --git a/test/scratch_classes/CMakeLists.txt b/test/scratch_classes/CMakeLists.txt
index 49bb075b..dfdd4c21 100644
--- a/test/scratch_classes/CMakeLists.txt
+++ b/test/scratch_classes/CMakeLists.txt
@@ -35,7 +35,9 @@ add_executable(
 target_link_libraries(
   sprite_test
   GTest::gtest_main
+  GTest::gmock_main
   scratchcpp
+  scratchcpp_mocks
 )
 
 gtest_discover_tests(sprite_test)
diff --git a/test/scratch_classes/sprite_test.cpp b/test/scratch_classes/sprite_test.cpp
index 3c130052..5580a73d 100644
--- a/test/scratch_classes/sprite_test.cpp
+++ b/test/scratch_classes/sprite_test.cpp
@@ -1,9 +1,15 @@
 #include <scratchcpp/sprite.h>
+#include <scratchcpp/variable.h>
+#include <scratchcpp/list.h>
+#include <enginemock.h>
 
 #include "../common.h"
 
 using namespace libscratchcpp;
 
+using ::testing::_;
+using ::testing::SaveArg;
+
 TEST(SpriteTest, IsStage)
 {
     Sprite sprite;
@@ -18,6 +24,113 @@ TEST(SpriteTest, Visible)
     ASSERT_FALSE(sprite.visible());
 }
 
+TEST(SpriteTest, Clone)
+{
+    Sprite sprite;
+    sprite.setName("Sprite1");
+    auto var1 = std::make_shared<Variable>("a", "var1", "hello");
+    auto var2 = std::make_shared<Variable>("b", "var2", "world");
+    sprite.addVariable(var1);
+    sprite.addVariable(var2);
+
+    auto list1 = std::make_shared<List>("c", "list1");
+    list1->push_back("item1");
+    list1->push_back("item2");
+    auto list2 = std::make_shared<List>("d", "list2");
+    list2->push_back("test");
+    sprite.addList(list1);
+    sprite.addList(list2);
+
+    sprite.setCurrentCostume(2);
+    sprite.setLayerOrder(5);
+    sprite.setVolume(50);
+
+    sprite.setVisible(false);
+    sprite.setX(100.25);
+    sprite.setY(-45.43);
+    sprite.setSize(54.121);
+    sprite.setDirection(179.4);
+    sprite.setDraggable(true);
+    sprite.setRotationStyle(Sprite::RotationStyle::DoNotRotate);
+
+    auto checkCloneData = [](Sprite *clone) {
+        ASSERT_TRUE(clone);
+        Sprite *root = clone->cloneRoot();
+
+        ASSERT_EQ(clone->name(), "Sprite1");
+        ASSERT_EQ(clone->variables().size(), 2);
+        ASSERT_NE(clone->variables(), root->variables());
+        ASSERT_EQ(clone->variableAt(0)->id(), "a");
+        ASSERT_EQ(clone->variableAt(0)->name(), "var1");
+        ASSERT_EQ(clone->variableAt(0)->value().toString(), "hello");
+        ASSERT_EQ(clone->variableAt(1)->id(), "b");
+        ASSERT_EQ(clone->variableAt(1)->name(), "var2");
+        ASSERT_EQ(clone->variableAt(1)->value().toString(), "world");
+
+        ASSERT_EQ(clone->lists().size(), 2);
+        ASSERT_NE(clone->lists(), root->lists());
+        ASSERT_EQ(clone->listAt(0)->id(), "c");
+        ASSERT_EQ(clone->listAt(0)->name(), "list1");
+        ASSERT_EQ(*clone->listAt(0), std::deque<Value>({ "item1", "item2" }));
+        ASSERT_EQ(clone->listAt(1)->id(), "d");
+        ASSERT_EQ(clone->listAt(1)->name(), "list2");
+        ASSERT_EQ(*clone->listAt(1), std::deque<Value>({ "test" }));
+
+        ASSERT_EQ(clone->currentCostume(), 2);
+        ASSERT_EQ(clone->layerOrder(), 5);
+        ASSERT_EQ(clone->volume(), 50);
+        ASSERT_EQ(clone->engine(), root->engine());
+
+        ASSERT_EQ(clone->visible(), false);
+        ASSERT_EQ(clone->x(), 100.25);
+        ASSERT_EQ(clone->y(), -45.43);
+        ASSERT_EQ(clone->size(), 54.121);
+        ASSERT_EQ(clone->direction(), 179.4);
+        ASSERT_EQ(clone->draggable(), true);
+        ASSERT_EQ(clone->rotationStyle(), Sprite::RotationStyle::DoNotRotate);
+    };
+
+    ASSERT_FALSE(sprite.isClone());
+    ASSERT_EQ(sprite.cloneRoot(), nullptr);
+    ASSERT_EQ(sprite.cloneParent(), nullptr);
+
+    ASSERT_EQ(sprite.clone(), nullptr);
+    ASSERT_FALSE(sprite.isClone());
+    ASSERT_EQ(sprite.cloneRoot(), nullptr);
+    ASSERT_EQ(sprite.cloneParent(), nullptr);
+
+    EngineMock engine;
+    sprite.setEngine(&engine);
+
+    Sprite *clone1;
+    EXPECT_CALL(engine, initClone(_)).WillOnce(SaveArg<0>(&clone1));
+    ASSERT_EQ(sprite.clone().get(), clone1);
+    ASSERT_FALSE(sprite.isClone());
+    ASSERT_EQ(sprite.cloneRoot(), nullptr);
+    ASSERT_EQ(sprite.cloneParent(), nullptr);
+
+    ASSERT_TRUE(clone1->isClone());
+    ASSERT_EQ(clone1->cloneRoot(), &sprite);
+    ASSERT_EQ(clone1->cloneParent(), &sprite);
+
+    checkCloneData(clone1);
+
+    // Modify root sprite data to make sure parent is used
+    sprite.setLayerOrder(3);
+
+    Sprite *clone2;
+    EXPECT_CALL(engine, initClone(_)).WillOnce(SaveArg<0>(&clone2));
+    ASSERT_EQ(clone1->clone().get(), clone2);
+    ASSERT_TRUE(clone1->isClone());
+    ASSERT_EQ(clone1->cloneRoot(), &sprite);
+    ASSERT_EQ(clone1->cloneParent(), &sprite);
+    ASSERT_TRUE(clone2->isClone());
+    ASSERT_EQ(clone2->cloneRoot(), &sprite);
+    ASSERT_EQ(clone2->cloneParent(), clone1);
+
+    checkCloneData(clone2);
+}
+
 TEST(SpriteTest, X)
 {
     Sprite sprite;

From b7035b75d12e23dc5e25ed732e58a9e6bdb28669 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sat, 2 Sep 2023 12:01:57 +0200
Subject: [PATCH 21/56] Add target getter to Script

---
 include/scratchcpp/script.h | 2 ++
 src/engine/script.cpp       | 6 ++++++
 test/script/script_test.cpp | 6 ++++++
 3 files changed, 14 insertions(+)

diff --git a/include/scratchcpp/script.h b/include/scratchcpp/script.h
index 0e01a80c..33a703a4 100644
--- a/include/scratchcpp/script.h
+++ b/include/scratchcpp/script.h
@@ -25,6 +25,8 @@ class LIBSCRATCHCPP_EXPORT Script
         Script(Target *target, IEngine *engine);
         Script(const Script &) = delete;
 
+        Target *target() const;
+
         unsigned int *bytecode() const;
         const std::vector<unsigned int> &bytecodeVector() const;
         void setBytecode(const std::vector<unsigned int> &code);
diff --git a/src/engine/script.cpp b/src/engine/script.cpp
index 729b363f..fca77219 100644
--- a/src/engine/script.cpp
+++ b/src/engine/script.cpp
@@ -13,6 +13,12 @@ Script::Script(Target *target, IEngine *engine) :
 {
 }
 
+/*! Returns the Target. */
+Target *Script::target() const
+{
+    return impl->target;
+}
+
 /*! Returns the bytecode array. */
 unsigned int *Script::bytecode() const
 {
diff --git a/test/script/script_test.cpp b/test/script/script_test.cpp
index d0d9ad6a..8dafccc8 100644
--- a/test/script/script_test.cpp
+++ b/test/script/script_test.cpp
@@ -15,6 +15,12 @@ class ScriptTest : public testing::Test
         EngineMock m_engine;
 };
 
+TEST_F(ScriptTest, Constructors)
+{
+    Script script(&m_target, &m_engine);
+    ASSERT_EQ(script.target(), &m_target);
+}
+
 TEST_F(ScriptTest, Bytecode)
 {
     Script script(nullptr, nullptr);

From 1db403e6dd4a69192a49b8fe9faeb7a292e92ee9 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sat, 2 Sep 2023 12:11:12 +0200
Subject: [PATCH 22/56] Add variable and list vector setters to VirtualMachine

---
 include/scratchcpp/virtualmachine.h           |  2 ++
 src/engine/virtualmachine.cpp                 | 20 +++++++++++++++++++
 src/engine/virtualmachine_p.h                 |  2 ++
 test/virtual_machine/virtual_machine_test.cpp | 19 ++++++++++++++----
 4 files changed, 39 insertions(+), 4 deletions(-)

diff --git a/include/scratchcpp/virtualmachine.h b/include/scratchcpp/virtualmachine.h
index 0e262cff..41dd40ea 100644
--- a/include/scratchcpp/virtualmachine.h
+++ b/include/scratchcpp/virtualmachine.h
@@ -102,6 +102,8 @@ class LIBSCRATCHCPP_EXPORT VirtualMachine
         void setConstValues(const Value *values);
         void setVariables(Value **variables);
         void setLists(List **lists);
+        void setVariablesVector(const std::vector<Value *> &variables);
+        void setListsVector(const std::vector<List *> &lists);
 
         void setBytecode(unsigned int *code);
 
diff --git a/src/engine/virtualmachine.cpp b/src/engine/virtualmachine.cpp
index dc9b3c58..c700c728 100644
--- a/src/engine/virtualmachine.cpp
+++ b/src/engine/virtualmachine.cpp
@@ -50,6 +50,26 @@ void VirtualMachine::setLists(List **lists)
     impl->lists = lists;
 }
 
+/*!
+ * Sets the vector of variables.
+ * Use this instead of setVariables() if you can't store the array (data pointer) anywhere.
+ */
+void VirtualMachine::setVariablesVector(const std::vector<Value *> &variables)
+{
+    impl->variablesVector = variables;
+    impl->variables = impl->variablesVector.data();
+}
+
+/*!
+ * Sets the vector of lists.
+ * Use this instead of setLists() if you can't store the array (data pointer) anywhere.
+ */
+void VirtualMachine::setListsVector(const std::vector<List *> &lists)
+{
+    impl->listsVector = lists;
+    impl->lists = impl->listsVector.data();
+}
+
 /*! Sets the bytecode of the script. */
 void VirtualMachine::setBytecode(unsigned int *code)
 {
diff --git a/src/engine/virtualmachine_p.h b/src/engine/virtualmachine_p.h
index af00076b..262926b7 100644
--- a/src/engine/virtualmachine_p.h
+++ b/src/engine/virtualmachine_p.h
@@ -59,7 +59,9 @@ struct VirtualMachinePrivate
         BlockFunc *functions = nullptr;
         const Value *constValues = nullptr;
         Value **variables = nullptr;
+        std::vector<Value *> variablesVector;
         List **lists = nullptr;
+        std::vector<List *> listsVector;
 
         Value **regs = nullptr;
         size_t regCount = 0;
diff --git a/test/virtual_machine/virtual_machine_test.cpp b/test/virtual_machine/virtual_machine_test.cpp
index aa34ec1a..bb692b03 100644
--- a/test/virtual_machine/virtual_machine_test.cpp
+++ b/test/virtual_machine/virtual_machine_test.cpp
@@ -48,24 +48,35 @@ TEST(VirtualMachineTest, ConstValues)
 
 TEST(VirtualMachineTest, Variables)
 {
-    Value var;
-    Value *variables[] = { &var };
+    Value var1, var2;
+    Value *variables[] = { &var1, &var2 };
 
     VirtualMachine vm;
     ASSERT_EQ(vm.variables(), nullptr);
+
     vm.setVariables(variables);
     ASSERT_EQ(vm.variables(), variables);
+
+    vm.setVariablesVector({ &var1, &var2 });
+    ASSERT_EQ(vm.variables()[0], &var1);
+    ASSERT_EQ(vm.variables()[1], &var2);
 }
 
 TEST(VirtualMachineTest, Lists)
 {
-    List list("", "");
-    List *lists[] = { &list };
+    List list1("", "");
+    List list2("", "");
+    List *lists[] = { &list1, &list2 };
 
     VirtualMachine vm;
     ASSERT_EQ(vm.lists(), nullptr);
+
     vm.setLists(lists);
     ASSERT_EQ(vm.lists(), lists);
+
+    vm.setListsVector({ &list1, &list2 });
+    ASSERT_EQ(vm.lists()[0], &list1);
+    ASSERT_EQ(vm.lists()[1], &list2);
 }
 
 TEST(VirtualMachineTest, Bytecode)

From 8792b255f930cc56cd3ddd01b3e43d3511de6e4c Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sat, 2 Sep 2023 13:40:04 +0200
Subject: [PATCH 23/56] Add a way to get variable and list owner target

---
 include/scratchcpp/iengine.h   |  8 ++++++
 src/engine/internal/engine.cpp | 38 ++++++++++++++++++++++++++-
 src/engine/internal/engine.h   |  5 ++++
 test/engine/engine_test.cpp    | 48 ++++++++++++++++++++++++++++++++++
 test/mocks/enginemock.h        |  3 +++
 5 files changed, 101 insertions(+), 1 deletion(-)

diff --git a/include/scratchcpp/iengine.h b/include/scratchcpp/iengine.h
index 94d6648a..c6d2312a 100644
--- a/include/scratchcpp/iengine.h
+++ b/include/scratchcpp/iengine.h
@@ -16,6 +16,8 @@ class Broadcast;
 class Block;
 class Target;
 class Sprite;
+class Variable;
+class List;
 class Script;
 
 /*!
@@ -176,6 +178,12 @@ class LIBSCRATCHCPP_EXPORT IEngine
         /*! Returns the index of the target with the given name. */
         virtual int findTarget(const std::string &targetName) const = 0;
 
+        /*! Returns the target which owns the given variable. If it is the stage, the variable is global. */
+        virtual Target *variableOwner(Variable *variable) const = 0;
+
+        /*! Returns the target which owns the given list. If it is the stage, the list is global. */
+        virtual Target *listOwner(List *list) const = 0;
+
         /*! Returns the list of extension names. */
         virtual const std::vector<std::string> &extensions() const = 0;
 
diff --git a/src/engine/internal/engine.cpp b/src/engine/internal/engine.cpp
index 6257c5a9..63c19097 100644
--- a/src/engine/internal/engine.cpp
+++ b/src/engine/internal/engine.cpp
@@ -509,15 +509,31 @@ const std::vector<std::shared_ptr<Target>> &Engine::targets() const
 void Engine::setTargets(const std::vector<std::shared_ptr<Target>> &newTargets)
 {
     m_targets = newTargets;
+    m_variableOwners.clear();
+    m_listOwners.clear();
 
-    // Set engine in targets and engine and target in all blocks
     for (auto target : m_targets) {
+        // Set engine in the target
         target->setEngine(this);
         auto blocks = target->blocks();
+
         for (auto block : blocks) {
+            // Set engine and target in the block
             block->setEngine(this);
             block->setTarget(target.get());
         }
+
+        // Add variables to owner map
+        const auto &variables = target->variables();
+
+        for (auto variable : variables)
+            m_variableOwners[variable.get()] = target.get();
+
+        // Add lists to owner map
+        const auto &lists = target->lists();
+
+        for (auto list : lists)
+            m_listOwners[list.get()] = target.get();
     }
 }
 
@@ -540,6 +556,26 @@ int Engine::findTarget(const std::string &targetName) const
     return -1;
 }
 
+Target *Engine::variableOwner(Variable *variable) const
+{
+    auto it = m_variableOwners.find(variable);
+
+    if (it == m_variableOwners.cend())
+        return nullptr;
+
+    return it->second;
+}
+
+Target *Engine::listOwner(List *list) const
+{
+    auto it = m_listOwners.find(list);
+
+    if (it == m_listOwners.cend())
+        return nullptr;
+
+    return it->second;
+}
+
 const std::vector<std::string> &Engine::extensions() const
 {
     return m_extensions;
diff --git a/src/engine/internal/engine.h b/src/engine/internal/engine.h
index b0e1640c..06980bbc 100644
--- a/src/engine/internal/engine.h
+++ b/src/engine/internal/engine.h
@@ -68,6 +68,9 @@ class Engine : public IEngine
         Target *targetAt(int index) const override;
         int findTarget(const std::string &targetName) const override;
 
+        Target *variableOwner(Variable *variable) const override;
+        Target *listOwner(List *list) const override;
+
         const std::vector<std::string> &extensions() const override;
         void setExtensions(const std::vector<std::string> &newExtensions) override;
 
@@ -95,6 +98,8 @@ class Engine : public IEngine
         std::vector<VirtualMachine *> m_scriptsToRemove;
         std::unordered_map<std::shared_ptr<Block>, std::shared_ptr<Script>> m_scripts;
         std::vector<BlockFunc> m_functions;
+        std::unordered_map<Variable *, Target *> m_variableOwners;
+        std::unordered_map<List *, Target *> m_listOwners;
 
         bool m_breakFrame = false;
         bool m_skipFrame = false;
diff --git a/test/engine/engine_test.cpp b/test/engine/engine_test.cpp
index 2b4ee447..f15364c9 100644
--- a/test/engine/engine_test.cpp
+++ b/test/engine/engine_test.cpp
@@ -1,5 +1,7 @@
 #include <scratchcpp/broadcast.h>
 #include <scratchcpp/block.h>
+#include <scratchcpp/variable.h>
+#include <scratchcpp/list.h>
 
 #include "../common.h"
 #include "testsection.h"
@@ -283,3 +285,49 @@ TEST(EngineTest, Targets)
     ASSERT_EQ(block1->engine(), &engine);
     ASSERT_EQ(block2->engine(), &engine);
 }
+
+TEST(EngineTest, VariableOwner)
+{
+    Engine engine;
+    auto t1 = std::make_shared<Target>();
+    auto t2 = std::make_shared<Target>();
+    auto t3 = std::make_shared<Target>();
+
+    auto var1 = std::make_shared<Variable>("", "", Value());
+    auto var2 = std::make_shared<Variable>("", "", Value());
+    auto var3 = std::make_shared<Variable>("", "", Value());
+    auto var4 = std::make_shared<Variable>("", "", Value());
+    t1->addVariable(var1);
+    t1->addVariable(var4);
+    t3->addVariable(var3);
+
+    engine.setTargets({ t1, t2, t3 });
+
+    ASSERT_EQ(engine.variableOwner(var1.get()), t1.get());
+    ASSERT_EQ(engine.variableOwner(var2.get()), nullptr);
+    ASSERT_EQ(engine.variableOwner(var3.get()), t3.get());
+    ASSERT_EQ(engine.variableOwner(var4.get()), t1.get());
+}
+
+TEST(EngineTest, ListOwner)
+{
+    Engine engine;
+    auto t1 = std::make_shared<Target>();
+    auto t2 = std::make_shared<Target>();
+    auto t3 = std::make_shared<Target>();
+
+    auto list1 = std::make_shared<List>("", "");
+    auto list2 = std::make_shared<List>("", "");
+    auto list3 = std::make_shared<List>("", "");
+    auto list4 = std::make_shared<List>("", "");
+    t1->addList(list1);
+    t1->addList(list4);
+    t3->addList(list3);
+
+    engine.setTargets({ t1, t2, t3 });
+
+    ASSERT_EQ(engine.listOwner(list1.get()), t1.get());
+    ASSERT_EQ(engine.listOwner(list2.get()), nullptr);
+    ASSERT_EQ(engine.listOwner(list3.get()), t3.get());
+    ASSERT_EQ(engine.listOwner(list4.get()), t1.get());
+}
diff --git a/test/mocks/enginemock.h b/test/mocks/enginemock.h
index 0632cd8d..99c20c4d 100644
--- a/test/mocks/enginemock.h
+++ b/test/mocks/enginemock.h
@@ -56,6 +56,9 @@ class EngineMock : public IEngine
         MOCK_METHOD(Target *, targetAt, (int), (const, override));
         MOCK_METHOD(int, findTarget, (const std::string &), (const, override));
 
+        MOCK_METHOD(Target *, variableOwner, (Variable *), (const, override));
+        MOCK_METHOD(Target *, listOwner, (List *), (const, override));
+
         MOCK_METHOD(std::vector<std::string> &, extensions, (), (const, override));
         MOCK_METHOD(void, setExtensions, (const std::vector<std::string> &), (override));
 

From 294c238873fec86c9b366118790e79b06ae33288 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sat, 2 Sep 2023 17:27:19 +0200
Subject: [PATCH 24/56] Use variables and lists from clones in Script

---
 include/scratchcpp/script.h    |  3 +-
 src/engine/internal/engine.cpp |  2 +-
 src/engine/script.cpp          | 73 ++++++++++++++++++++++++++++++----
 src/engine/script_p.h          |  8 ++--
 test/script/script_test.cpp    | 51 +++++++++++++++++++++---
 5 files changed, 117 insertions(+), 20 deletions(-)

diff --git a/include/scratchcpp/script.h b/include/scratchcpp/script.h
index 33a703a4..78afd2d4 100644
--- a/include/scratchcpp/script.h
+++ b/include/scratchcpp/script.h
@@ -15,6 +15,7 @@ class Target;
 class IEngine;
 class Value;
 class VirtualMachine;
+class Variable;
 class List;
 class ScriptPrivate;
 
@@ -34,7 +35,7 @@ class LIBSCRATCHCPP_EXPORT Script
         void setProcedures(const std::vector<unsigned int *> &procedures);
         void setFunctions(const std::vector<BlockFunc> &functions);
         void setConstValues(const std::vector<Value> &values);
-        void setVariables(const std::vector<Value *> &variables);
+        void setVariables(const std::vector<Variable *> &variables);
         void setLists(const std::vector<List *> &lists);
 
         std::shared_ptr<VirtualMachine> start();
diff --git a/src/engine/internal/engine.cpp b/src/engine/internal/engine.cpp
index 63c19097..92fcafc3 100644
--- a/src/engine/internal/engine.cpp
+++ b/src/engine/internal/engine.cpp
@@ -112,7 +112,7 @@ void Engine::compile()
             if (m_scripts.count(block) == 1) {
                 m_scripts[block]->setProcedures(procedureBytecodes);
                 m_scripts[block]->setConstValues(compiler.constValues());
-                m_scripts[block]->setVariables(compiler.variablePtrs());
+                m_scripts[block]->setVariables(compiler.variables());
                 m_scripts[block]->setLists(compiler.lists());
             }
         }
diff --git a/src/engine/script.cpp b/src/engine/script.cpp
index fca77219..14e2c9e1 100644
--- a/src/engine/script.cpp
+++ b/src/engine/script.cpp
@@ -2,6 +2,11 @@
 
 #include <scratchcpp/script.h>
 #include <scratchcpp/virtualmachine.h>
+#include <scratchcpp/variable.h>
+#include <scratchcpp/list.h>
+#include <scratchcpp/sprite.h>
+#include <scratchcpp/iengine.h>
+#include <iostream>
 
 #include "script_p.h"
 
@@ -44,7 +49,7 @@ std::shared_ptr<VirtualMachine> Script::start()
     return start(impl->target);
 }
 
-/*! Starts the script (creates a virtual machine). */
+/*! Starts the script (creates a virtual machine) as the given target (might be a clone). */
 std::shared_ptr<VirtualMachine> Script::start(Target *target)
 {
     auto vm = std::make_shared<VirtualMachine>(target, impl->engine, this);
@@ -52,8 +57,58 @@ std::shared_ptr<VirtualMachine> Script::start(Target *target)
     vm->setProcedures(impl->procedures);
     vm->setFunctions(impl->functions);
     vm->setConstValues(impl->constValues);
-    vm->setVariables(impl->variables);
-    vm->setLists(impl->lists);
+
+    Sprite *sprite = nullptr;
+    if (target && !target->isStage())
+        sprite = dynamic_cast<Sprite *>(target);
+
+    if (impl->target && sprite && sprite->isClone() && impl->engine) {
+        Target *root = sprite->cloneRoot();
+
+        if (root != impl->target) {
+            std::cout << "warning: a clone tried to start a script of another target (this is a bug in libscratchcpp or in your code!)" << std::endl;
+            vm->setVariables(impl->variableValues.data());
+            vm->setLists(impl->lists.data());
+            return vm;
+        }
+
+        // Use internal variables and lists from the clone
+        std::vector<Value *> variables;
+        std::vector<List *> lists;
+
+        for (const auto &var : impl->variables) {
+            Target *owner = impl->engine->variableOwner(var);
+
+            if (owner && (owner == root)) {
+                auto cloneVar = sprite->variableAt(sprite->findVariableById(var->id()));
+                assert(cloneVar);
+
+                if (cloneVar)
+                    variables.push_back(cloneVar->valuePtr());
+            } else
+                variables.push_back(var->valuePtr());
+        }
+
+        for (const auto &list : impl->lists) {
+            Target *owner = impl->engine->listOwner(list);
+
+            if (owner && (owner == root)) {
+                auto cloneList = sprite->listAt(sprite->findListById(list->id()));
+                assert(cloneList);
+
+                if (cloneList)
+                    lists.push_back(cloneList.get());
+            } else
+                lists.push_back(list);
+        }
+
+        vm->setVariablesVector(variables);
+        vm->setListsVector(lists);
+    } else {
+        vm->setVariables(impl->variableValues.data());
+        vm->setLists(impl->lists.data());
+    }
+
     return vm;
 }
 
@@ -79,15 +134,17 @@ void Script::setConstValues(const std::vector<Value> &values)
 }
 
 /*! Sets the list of variables. */
-void Script::setVariables(const std::vector<Value *> &variables)
+void Script::setVariables(const std::vector<Variable *> &variables)
 {
-    impl->variablesVector = variables;
-    impl->variables = impl->variablesVector.data();
+    impl->variables = variables;
+    impl->variableValues.clear();
+
+    for (const auto &var : variables)
+        impl->variableValues.push_back(var->valuePtr());
 }
 
 /*! Sets the list of lists. */
 void Script::setLists(const std::vector<List *> &lists)
 {
-    impl->listsVector = lists;
-    impl->lists = impl->listsVector.data();
+    impl->lists = lists;
 }
diff --git a/src/engine/script_p.h b/src/engine/script_p.h
index a67e4f39..7b643429 100644
--- a/src/engine/script_p.h
+++ b/src/engine/script_p.h
@@ -12,6 +12,7 @@ namespace libscratchcpp
 
 class Target;
 class IEngine;
+class Variable;
 class List;
 
 struct ScriptPrivate
@@ -34,11 +35,10 @@ struct ScriptPrivate
         const Value *constValues = nullptr;
         std::vector<Value> constValuesVector;
 
-        Value **variables = nullptr;
-        std::vector<Value *> variablesVector;
+        std::vector<Value *> variableValues;
+        std::vector<Variable *> variables;
 
-        List **lists = nullptr;
-        std::vector<List *> listsVector;
+        std::vector<List *> lists;
 };
 
 } // namespace libscratchcpp
diff --git a/test/script/script_test.cpp b/test/script/script_test.cpp
index 8dafccc8..caca86a3 100644
--- a/test/script/script_test.cpp
+++ b/test/script/script_test.cpp
@@ -1,6 +1,8 @@
 #include <scratchcpp/script.h>
 #include <scratchcpp/virtualmachine.h>
 #include <scratchcpp/target.h>
+#include <scratchcpp/sprite.h>
+#include <scratchcpp/variable.h>
 #include <scratchcpp/list.h>
 #include <enginemock.h>
 
@@ -8,6 +10,8 @@
 
 using namespace libscratchcpp;
 
+using ::testing::Return;
+
 class ScriptTest : public testing::Test
 {
     public:
@@ -45,11 +49,13 @@ TEST_F(ScriptTest, Start)
     static std::vector<BlockFunc> functions = { &testFunction };
     static std::vector<Value> constValues = { "test" };
 
-    std::unique_ptr<Value> var = std::make_unique<Value>();
-    static std::vector<Value *> variables = { var.get() };
+    std::unique_ptr<Variable> var1 = std::make_unique<Variable>("a", "", Value());
+    std::unique_ptr<Variable> var2 = std::make_unique<Variable>("b", "", Value());
+    static std::vector<Variable *> variables = { var1.get(), var2.get() };
 
-    std::unique_ptr<List> list = std::make_unique<List>("", "");
-    static std::vector<List *> lists = { list.get() };
+    std::unique_ptr<List> list1 = std::make_unique<List>("c", "");
+    std::unique_ptr<List> list2 = std::make_unique<List>("d", "");
+    static std::vector<List *> lists = { list1.get(), list2.get() };
 
     Script script1(nullptr, nullptr);
 
@@ -85,7 +91,7 @@ TEST_F(ScriptTest, Start)
     ASSERT_EQ(vm->procedures()[0], procedures[0]);
     ASSERT_EQ(vm->functions()[0], functions[0]);
     ASSERT_EQ(vm->constValues()[0].toString(), constValues[0].toString());
-    ASSERT_EQ(vm->variables()[0], variables[0]);
+    ASSERT_EQ(vm->variables()[0], variables[0]->valuePtr());
     ASSERT_EQ(vm->lists()[0], lists[0]);
 
     Target target;
@@ -96,6 +102,39 @@ TEST_F(ScriptTest, Start)
     ASSERT_EQ(vm->procedures()[0], procedures[0]);
     ASSERT_EQ(vm->functions()[0], functions[0]);
     ASSERT_EQ(vm->constValues()[0].toString(), constValues[0].toString());
-    ASSERT_EQ(vm->variables()[0], variables[0]);
+    ASSERT_EQ(vm->variables()[0], variables[0]->valuePtr());
+    ASSERT_EQ(vm->lists()[0], lists[0]);
+
+    Sprite root;
+    root.setEngine(&m_engine);
+    root.addVariable(std::make_shared<Variable>("b", "", Value()));
+    root.addList(std::make_shared<List>("d", ""));
+
+    EXPECT_CALL(m_engine, initClone).Times(1);
+    auto clone = root.clone();
+
+    Script script4(&root, &m_engine);
+    script4.setBytecode(bytecode);
+    script4.setProcedures(procedures);
+    script4.setFunctions(functions);
+    script4.setConstValues(constValues);
+    script4.setVariables(variables);
+    script4.setLists(lists);
+
+    EXPECT_CALL(m_engine, variableOwner(var1.get())).WillOnce(Return(&target));
+    EXPECT_CALL(m_engine, variableOwner(var2.get())).WillOnce(Return(&root));
+    EXPECT_CALL(m_engine, listOwner(list1.get())).WillOnce(Return(&target));
+    EXPECT_CALL(m_engine, listOwner(list2.get())).WillOnce(Return(&root));
+    vm = script4.start(clone.get());
+
+    ASSERT_TRUE(vm);
+    ASSERT_EQ(vm->target(), clone.get());
+    ASSERT_EQ(vm->bytecode()[0], bytecode[0]);
+    ASSERT_EQ(vm->procedures()[0], procedures[0]);
+    ASSERT_EQ(vm->functions()[0], functions[0]);
+    ASSERT_EQ(vm->constValues()[0].toString(), constValues[0].toString());
+    ASSERT_EQ(vm->variables()[0], variables[0]->valuePtr());
+    ASSERT_EQ(vm->variables()[1], clone->variableAt(clone->findVariableById("b"))->valuePtr());
     ASSERT_EQ(vm->lists()[0], lists[0]);
+    ASSERT_EQ(vm->lists()[1], clone->listAt(clone->findListById("d")).get());
 }

From c4538e0d9961bb8b0d294227d76e98b7ae821c5a Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sat, 2 Sep 2023 17:53:28 +0200
Subject: [PATCH 25/56] Add Sprite children getters

---
 include/scratchcpp/sprite.h          |  2 ++
 src/scratch/sprite.cpp               | 20 +++++++++++++
 test/scratch_classes/sprite_test.cpp | 44 ++++++++++++++++++++++++++++
 3 files changed, 66 insertions(+)

diff --git a/include/scratchcpp/sprite.h b/include/scratchcpp/sprite.h
index 356e0799..9597bf90 100644
--- a/include/scratchcpp/sprite.h
+++ b/include/scratchcpp/sprite.h
@@ -32,6 +32,8 @@ class LIBSCRATCHCPP_EXPORT Sprite : public Target
 
         Sprite *cloneRoot() const;
         Sprite *cloneParent() const;
+        const std::vector<std::shared_ptr<Sprite>> &children() const;
+        std::vector<std::shared_ptr<Sprite>> allChildren() const;
 
         bool visible() const;
         void setVisible(bool newVisible);
diff --git a/src/scratch/sprite.cpp b/src/scratch/sprite.cpp
index 5ffaa657..83437c8f 100644
--- a/src/scratch/sprite.cpp
+++ b/src/scratch/sprite.cpp
@@ -109,6 +109,26 @@ Sprite *Sprite::cloneParent() const
     return impl->cloneParent;
 }
 
+/*! Returns list of child clones. */
+const std::vector<std::shared_ptr<Sprite>> &Sprite::children() const
+{
+    return impl->childClones;
+}
+
+/*! Returns list of child clones and their children (recursive). */
+std::vector<std::shared_ptr<Sprite>> Sprite::allChildren() const
+{
+    std::vector<std::shared_ptr<Sprite>> ret;
+
+    for (auto clone : impl->childClones) {
+        ret.push_back(clone);
+        auto children = clone->allChildren();
+        ret.insert(ret.end(), children.begin(), children.end());
+    }
+
+    return ret;
+}
+
 /*! Returns true if the sprite is visible. */
 bool Sprite::visible() const
 {
diff --git a/test/scratch_classes/sprite_test.cpp b/test/scratch_classes/sprite_test.cpp
index 5580a73d..1097fc7f 100644
--- a/test/scratch_classes/sprite_test.cpp
+++ b/test/scratch_classes/sprite_test.cpp
@@ -129,6 +129,50 @@ TEST(SpriteTest, Clone)
     ASSERT_EQ(clone2->cloneParent(), clone1);
 
     checkCloneData(clone2);
+
+    Sprite *clone3;
+    EXPECT_CALL(engine, initClone(_)).WillOnce(SaveArg<0>(&clone3));
+    ASSERT_EQ(clone1->clone().get(), clone3);
+
+    Sprite *clone4;
+    EXPECT_CALL(engine, initClone(_)).WillOnce(SaveArg<0>(&clone4));
+    ASSERT_EQ(sprite.clone().get(), clone4);
+
+    // children
+    const auto &children1 = sprite.children();
+    ASSERT_EQ(children1.size(), 2);
+    ASSERT_EQ(children1[0].get(), clone1);
+    ASSERT_EQ(children1[1].get(), clone4);
+
+    const auto &children2 = clone1->children();
+    ASSERT_EQ(children2.size(), 2);
+    ASSERT_EQ(children2[0].get(), clone2);
+    ASSERT_EQ(children2[1].get(), clone3);
+
+    ASSERT_TRUE(clone2->children().empty());
+
+    ASSERT_TRUE(clone3->children().empty());
+
+    ASSERT_TRUE(clone4->children().empty());
+
+    // allChildren
+    auto allChildren = sprite.allChildren();
+    ASSERT_EQ(allChildren.size(), 4);
+    ASSERT_EQ(allChildren[0].get(), clone1);
+    ASSERT_EQ(allChildren[1].get(), clone2);
+    ASSERT_EQ(allChildren[2].get(), clone3);
+    ASSERT_EQ(allChildren[3].get(), clone4);
+
+    allChildren = clone1->allChildren();
+    ASSERT_EQ(allChildren.size(), 2);
+    ASSERT_EQ(allChildren[0].get(), clone2);
+    ASSERT_EQ(allChildren[1].get(), clone3);
+
+    ASSERT_TRUE(clone2->allChildren().empty());
+
+    ASSERT_TRUE(clone3->allChildren().empty());
+
+    ASSERT_TRUE(clone4->allChildren().empty());
 }
 
 TEST(SpriteTest, X)

From 763c2c96468e434af97c575f8ac1b163f3f17bf8 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sat, 2 Sep 2023 17:56:49 +0200
Subject: [PATCH 26/56] Engine: Make clones receive broadcasts

---
 src/engine/internal/engine.cpp | 46 +++++++++++++++++++++++-----------
 1 file changed, 32 insertions(+), 14 deletions(-)

diff --git a/src/engine/internal/engine.cpp b/src/engine/internal/engine.cpp
index 92fcafc3..3572e4fa 100644
--- a/src/engine/internal/engine.cpp
+++ b/src/engine/internal/engine.cpp
@@ -202,22 +202,21 @@ void Engine::broadcast(unsigned int index, VirtualMachine *sourceScript, bool wa
     const std::vector<Script *> &scripts = m_broadcastMap[index];
 
     for (auto script : scripts) {
-        long scriptIndex = -1;
-        for (long i = 0; i < m_runningScripts.size(); i++) {
-            if (m_runningScripts[i]->script() == script) {
-                scriptIndex = i;
-                break;
+        std::vector<VirtualMachine *> runningBroadcastScripts;
+
+        for (auto vm : m_runningScripts) {
+            if (vm->script() == script) {
+                runningBroadcastScripts.push_back(vm.get());
             }
         }
 
-        if (scriptIndex != -1) {
-            // Reset the script if it's already running
-            auto vm = m_runningScripts[scriptIndex];
+        // Reset running scripts
+        for (VirtualMachine *vm : runningBroadcastScripts) {
             vm->reset();
 
             // Remove the script from scripts to remove because it's going to run again
-            m_scriptsToRemove.erase(std::remove(m_scriptsToRemove.begin(), m_scriptsToRemove.end(), vm.get()), m_scriptsToRemove.end());
-            assert(std::find(m_scriptsToRemove.begin(), m_scriptsToRemove.end(), m_runningScripts[scriptIndex].get()) == m_scriptsToRemove.end());
+            m_scriptsToRemove.erase(std::remove(m_scriptsToRemove.begin(), m_scriptsToRemove.end(), vm), m_scriptsToRemove.end());
+            assert(std::find(m_scriptsToRemove.begin(), m_scriptsToRemove.end(), vm) == m_scriptsToRemove.end());
 
             auto &scripts = m_runningBroadcastMap[index];
 
@@ -233,10 +232,29 @@ void Engine::broadcast(unsigned int index, VirtualMachine *sourceScript, bool wa
                     m_skipFrame = false;
             } else
                 sourceScript->stop(true, true);
-        } else {
-            auto vm = script->start();
-            m_runningScripts.push_back(vm);
-            m_runningBroadcastMap[index].push_back({ sourceScript, vm.get() });
+        }
+
+        // Start scripts which are not running
+        Target *root = script->target();
+        std::vector<Target *> targets = { root };
+
+        if (!root->isStage()) {
+            Sprite *sprite = dynamic_cast<Sprite *>(root);
+            assert(sprite);
+            auto children = sprite->allChildren();
+
+            for (auto child : children)
+                targets.push_back(child.get());
+        }
+
+        for (Target *target : targets) {
+            auto it = std::find_if(runningBroadcastScripts.begin(), runningBroadcastScripts.end(), [target](VirtualMachine *vm) { return vm->target() == target; });
+
+            if (it == runningBroadcastScripts.end()) {
+                auto vm = script->start(target);
+                m_runningScripts.push_back(vm);
+                m_runningBroadcastMap[index].push_back({ sourceScript, vm.get() });
+            }
         }
     }
 }

From 41f8b4970b75c400051e4252b69c6eb518c4acbd Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sat, 2 Sep 2023 21:33:52 +0200
Subject: [PATCH 27/56] Add a way to change the data source for blocks and
 assets

---
 include/scratchcpp/target.h          |   4 +
 src/scratch/target.cpp               |  40 +++++-
 test/mocks/targetmock.h              |  14 ++
 test/scratch_classes/target_test.cpp | 185 ++++++++++++++++++++++++++-
 4 files changed, 239 insertions(+), 4 deletions(-)
 create mode 100644 test/mocks/targetmock.h

diff --git a/include/scratchcpp/target.h b/include/scratchcpp/target.h
index 7262394b..fe9e7e23 100644
--- a/include/scratchcpp/target.h
+++ b/include/scratchcpp/target.h
@@ -71,6 +71,10 @@ class LIBSCRATCHCPP_EXPORT Target
         IEngine *engine() const;
         void setEngine(IEngine *engine);
 
+    protected:
+        /*! Override this method to set a custom data source for blocks and assets. */
+        virtual Target *dataSource() const { return nullptr; }
+
     private:
         spimpl::unique_impl_ptr<TargetPrivate> impl;
 };
diff --git a/src/scratch/target.cpp b/src/scratch/target.cpp
index 5555b403..05743f7e 100644
--- a/src/scratch/target.cpp
+++ b/src/scratch/target.cpp
@@ -132,12 +132,18 @@ int Target::findListById(const std::string &id) const
 /*! Returns the list of blocks. */
 const std::vector<std::shared_ptr<Block>> &Target::blocks() const
 {
+    if (Target *source = dataSource())
+        return source->blocks();
+
     return impl->blocks;
 }
 
 /*! Adds a block and returns its index. */
 int Target::addBlock(std::shared_ptr<Block> block)
 {
+    if (Target *source = dataSource())
+        return source->addBlock(block);
+
     auto it = std::find(impl->blocks.begin(), impl->blocks.end(), block);
 
     if (it != impl->blocks.end())
@@ -150,6 +156,9 @@ int Target::addBlock(std::shared_ptr<Block> block)
 /*! Returns the block at index. */
 std::shared_ptr<Block> Target::blockAt(int index) const
 {
+    if (Target *source = dataSource())
+        return source->blockAt(index);
+
     if (index < 0 || index >= impl->blocks.size())
         return nullptr;
 
@@ -159,6 +168,9 @@ std::shared_ptr<Block> Target::blockAt(int index) const
 /*! Returns the index of the block with the given ID. */
 int Target::findBlock(const std::string &id) const
 {
+    if (Target *source = dataSource())
+        return source->findBlock(id);
+
     int i = 0;
     for (auto block : impl->blocks) {
         if (block->id() == id)
@@ -172,7 +184,9 @@ int Target::findBlock(const std::string &id) const
 std::vector<std::shared_ptr<Block>> Target::greenFlagBlocks() const
 {
     std::vector<std::shared_ptr<Block>> ret;
-    for (auto block : impl->blocks) {
+    const auto &blockList = blocks();
+
+    for (auto block : blockList) {
         if (block->opcode() == "event_whenflagclicked")
             ret.push_back(block);
     }
@@ -195,12 +209,18 @@ void Target::setCurrentCostume(int newCostume)
 /*! Returns the list of costumes. */
 const std::vector<std::shared_ptr<Costume>> &Target::costumes() const
 {
+    if (Target *source = dataSource())
+        return source->costumes();
+
     return impl->costumes;
 }
 
 /*! Adds a costume and returns its index. */
 int Target::addCostume(std::shared_ptr<Costume> costume)
 {
+    if (Target *source = dataSource())
+        return source->addCostume(costume);
+
     auto it = std::find(impl->costumes.begin(), impl->costumes.end(), costume);
 
     if (it != impl->costumes.end())
@@ -213,6 +233,9 @@ int Target::addCostume(std::shared_ptr<Costume> costume)
 /*! Returns the costume at index. */
 std::shared_ptr<Costume> Target::costumeAt(int index) const
 {
+    if (Target *source = dataSource())
+        return source->costumeAt(index);
+
     // TODO: Add range check
     return impl->costumes[index];
 }
@@ -220,6 +243,9 @@ std::shared_ptr<Costume> Target::costumeAt(int index) const
 /*! Returns the index of the given costume. */
 int Target::findCostume(const std::string &costumeName) const
 {
+    if (Target *source = dataSource())
+        return source->findCostume(costumeName);
+
     int i = 0;
     for (auto costume : impl->costumes) {
         if (costume->name() == costumeName)
@@ -232,12 +258,18 @@ int Target::findCostume(const std::string &costumeName) const
 /*! Returns the list of sounds. */
 const std::vector<std::shared_ptr<Sound>> &Target::sounds() const
 {
+    if (Target *source = dataSource())
+        return source->sounds();
+
     return impl->sounds;
 }
 
 /*! Adds a sound and returns its index. */
 int Target::addSound(std::shared_ptr<Sound> sound)
 {
+    if (Target *source = dataSource())
+        return source->addSound(sound);
+
     auto it = std::find(impl->sounds.begin(), impl->sounds.end(), sound);
 
     if (it != impl->sounds.end())
@@ -250,6 +282,9 @@ int Target::addSound(std::shared_ptr<Sound> sound)
 /*! Returns the sound at index. */
 std::shared_ptr<Sound> Target::soundAt(int index) const
 {
+    if (Target *source = dataSource())
+        return source->soundAt(index);
+
     // TODO: Add range check
     return impl->sounds[index];
 }
@@ -257,6 +292,9 @@ std::shared_ptr<Sound> Target::soundAt(int index) const
 /*! Returns the index of the sound with the given name. */
 int Target::findSound(const std::string &soundName) const
 {
+    if (Target *source = dataSource())
+        return source->findSound(soundName);
+
     int i = 0;
     for (auto sound : impl->sounds) {
         if (sound->name() == soundName)
diff --git a/test/mocks/targetmock.h b/test/mocks/targetmock.h
new file mode 100644
index 00000000..d1ee5459
--- /dev/null
+++ b/test/mocks/targetmock.h
@@ -0,0 +1,14 @@
+#pragma once
+
+#include <scratchcpp/target.h>
+#include <gmock/gmock.h>
+
+using namespace libscratchcpp;
+
+class TargetMock : public Target
+{
+    public:
+        MOCK_METHOD(Target *, dataSource, (), (const override));
+
+        Target *dataSourceBase() const { return Target::dataSource(); };
+};
diff --git a/test/scratch_classes/target_test.cpp b/test/scratch_classes/target_test.cpp
index ce623368..821a5ff4 100644
--- a/test/scratch_classes/target_test.cpp
+++ b/test/scratch_classes/target_test.cpp
@@ -5,11 +5,14 @@
 #include <scratchcpp/costume.h>
 #include <scratchcpp/sound.h>
 #include <enginemock.h>
+#include <targetmock.h>
 
 #include "../common.h"
 
 using namespace libscratchcpp;
 
+using ::testing::Return;
+
 TEST(TargetTest, IsStage)
 {
     Target target;
@@ -90,7 +93,9 @@ TEST(TargetTest, Blocks)
     auto b3 = std::make_shared<Block>("c", "motion_ifonedgebounce");
     auto b4 = std::make_shared<Block>("d", "event_whenflagclicked");
 
-    Target target;
+    TargetMock target;
+    EXPECT_CALL(target, dataSource()).Times(18).WillRepeatedly(Return(nullptr));
+
     ASSERT_EQ(target.addBlock(b1), 0);
     ASSERT_EQ(target.addBlock(b2), 1);
     ASSERT_EQ(target.addBlock(b3), 2);
@@ -112,6 +117,62 @@ TEST(TargetTest, Blocks)
     ASSERT_EQ(target.findBlock("d"), 3);
 
     ASSERT_EQ(target.greenFlagBlocks(), std::vector<std::shared_ptr<Block>>({ b1, b4 }));
+
+    // Test with custom data source
+    Target source;
+
+    EXPECT_CALL(target, dataSource()).WillOnce(Return(&source));
+
+    ASSERT_TRUE(target.blocks().empty());
+
+    TargetMock target2;
+    EXPECT_CALL(target2, dataSource()).Times(18).WillRepeatedly(Return(&source));
+
+    ASSERT_EQ(target2.addBlock(b1), 0);
+    ASSERT_EQ(target2.addBlock(b2), 1);
+    ASSERT_EQ(target2.addBlock(b3), 2);
+    ASSERT_EQ(target2.addBlock(b4), 3);
+    ASSERT_EQ(target2.addBlock(b2), 1); // add existing block
+
+    ASSERT_EQ(target2.blockAt(0), b1);
+    ASSERT_EQ(target2.blockAt(1), b2);
+    ASSERT_EQ(target2.blockAt(2), b3);
+    ASSERT_EQ(target2.blockAt(3), b4);
+    ASSERT_EQ(target2.blockAt(4), nullptr);
+    ASSERT_EQ(target2.blockAt(-1), nullptr);
+
+    ASSERT_EQ(target2.findBlock("e"), -1);
+    ASSERT_EQ(target2.findBlock("a"), 0);
+    ASSERT_EQ(target2.findBlock("b"), 1);
+    ASSERT_EQ(target2.findBlock("c"), 2);
+    ASSERT_EQ(target2.findBlock("d"), 3);
+
+    ASSERT_EQ(target2.greenFlagBlocks(), std::vector<std::shared_ptr<Block>>({ b1, b4 }));
+
+    ASSERT_EQ(target2.blocks(), source.blocks());
+
+    auto b5 = std::make_shared<Block>("e", "data_setvariableto");
+    ASSERT_EQ(source.addBlock(b5), 4);
+
+    EXPECT_CALL(target2, dataSource()).WillOnce(Return(&source));
+    ASSERT_EQ(target2.blocks(), source.blocks());
+
+    ASSERT_EQ(source.blockAt(0), b1);
+    ASSERT_EQ(source.blockAt(1), b2);
+    ASSERT_EQ(source.blockAt(2), b3);
+    ASSERT_EQ(source.blockAt(3), b4);
+    ASSERT_EQ(source.blockAt(4), b5);
+    ASSERT_EQ(source.blockAt(5), nullptr);
+    ASSERT_EQ(source.blockAt(-1), nullptr);
+
+    ASSERT_EQ(source.findBlock("f"), -1);
+    ASSERT_EQ(source.findBlock("a"), 0);
+    ASSERT_EQ(source.findBlock("b"), 1);
+    ASSERT_EQ(source.findBlock("c"), 2);
+    ASSERT_EQ(source.findBlock("d"), 3);
+    ASSERT_EQ(source.findBlock("e"), 4);
+
+    ASSERT_EQ(source.greenFlagBlocks(), std::vector<std::shared_ptr<Block>>({ b1, b4 }));
 }
 
 TEST(TargetTest, CurrentCostume)
@@ -128,7 +189,9 @@ TEST(TargetTest, Costumes)
     auto c2 = std::make_shared<Costume>("costume2", "", "png");
     auto c3 = std::make_shared<Costume>("costume3", "", "svg");
 
-    Target target;
+    TargetMock target;
+    EXPECT_CALL(target, dataSource()).Times(15).WillRepeatedly(Return(nullptr));
+
     ASSERT_EQ(target.addCostume(c1), 0);
     ASSERT_EQ(target.addCostume(c2), 1);
     ASSERT_EQ(target.addCostume(c3), 2);
@@ -149,6 +212,60 @@ TEST(TargetTest, Costumes)
     ASSERT_EQ(target.findCostume("costume1"), 0);
     ASSERT_EQ(target.findCostume("costume2"), 1);
     ASSERT_EQ(target.findCostume("costume3"), 2);
+
+    // Test with custom data source
+    Target source;
+
+    EXPECT_CALL(target, dataSource()).WillOnce(Return(&source));
+
+    ASSERT_TRUE(target.costumes().empty());
+
+    TargetMock target2;
+    EXPECT_CALL(target2, dataSource()).Times(16).WillRepeatedly(Return(&source));
+
+    ASSERT_EQ(target2.addCostume(c1), 0);
+    ASSERT_EQ(target2.addCostume(c2), 1);
+    ASSERT_EQ(target2.addCostume(c3), 2);
+    ASSERT_EQ(target2.addCostume(c2), 1); // add existing costume
+
+    ASSERT_EQ(target2.costumes().size(), 3);
+    ASSERT_EQ(target2.costumes()[0]->name(), c1->name());
+    ASSERT_EQ(target2.costumes()[1]->name(), c2->name());
+    ASSERT_EQ(target2.costumes()[2]->name(), c3->name());
+
+    ASSERT_EQ(target2.costumeAt(0)->name(), c1->name());
+    ASSERT_EQ(target2.costumeAt(1)->name(), c2->name());
+    ASSERT_EQ(target2.costumeAt(2)->name(), c3->name());
+
+    ASSERT_EQ(target2.findCostume("invalid"), -1);
+    ASSERT_EQ(target2.findCostume("costume1"), 0);
+    ASSERT_EQ(target2.findCostume("costume2"), 1);
+    ASSERT_EQ(target2.findCostume("costume3"), 2);
+
+    ASSERT_EQ(target2.costumes(), source.costumes());
+
+    auto c4 = std::make_shared<Costume>("costume4", "", "png");
+    ASSERT_EQ(source.addCostume(c4), 3);
+
+    EXPECT_CALL(target2, dataSource()).WillOnce(Return(&source));
+    ASSERT_EQ(target2.costumes(), source.costumes());
+
+    ASSERT_EQ(source.costumes().size(), 4);
+    ASSERT_EQ(source.costumes()[0]->name(), c1->name());
+    ASSERT_EQ(source.costumes()[1]->name(), c2->name());
+    ASSERT_EQ(source.costumes()[2]->name(), c3->name());
+    ASSERT_EQ(source.costumes()[3]->name(), c4->name());
+
+    ASSERT_EQ(source.costumeAt(0)->name(), c1->name());
+    ASSERT_EQ(source.costumeAt(1)->name(), c2->name());
+    ASSERT_EQ(source.costumeAt(2)->name(), c3->name());
+    ASSERT_EQ(source.costumeAt(3)->name(), c4->name());
+
+    ASSERT_EQ(source.findCostume("invalid"), -1);
+    ASSERT_EQ(source.findCostume("costume1"), 0);
+    ASSERT_EQ(source.findCostume("costume2"), 1);
+    ASSERT_EQ(source.findCostume("costume3"), 2);
+    ASSERT_EQ(source.findCostume("costume4"), 3);
 }
 
 TEST(TargetTest, Sounds)
@@ -157,7 +274,9 @@ TEST(TargetTest, Sounds)
     auto s2 = std::make_shared<Sound>("sound2", "", "wav");
     auto s3 = std::make_shared<Sound>("sound3", "", "mp3");
 
-    Target target;
+    TargetMock target;
+    EXPECT_CALL(target, dataSource()).Times(15).WillRepeatedly(Return(nullptr));
+
     ASSERT_EQ(target.addSound(s1), 0);
     ASSERT_EQ(target.addSound(s2), 1);
     ASSERT_EQ(target.addSound(s3), 2);
@@ -178,6 +297,60 @@ TEST(TargetTest, Sounds)
     ASSERT_EQ(target.findSound("sound1"), 0);
     ASSERT_EQ(target.findSound("sound2"), 1);
     ASSERT_EQ(target.findSound("sound3"), 2);
+
+    // Test with custom data source
+    Target source;
+
+    EXPECT_CALL(target, dataSource()).WillOnce(Return(&source));
+
+    ASSERT_TRUE(target.sounds().empty());
+
+    TargetMock target2;
+    EXPECT_CALL(target2, dataSource()).Times(16).WillRepeatedly(Return(&source));
+
+    ASSERT_EQ(target2.addSound(s1), 0);
+    ASSERT_EQ(target2.addSound(s2), 1);
+    ASSERT_EQ(target2.addSound(s3), 2);
+    ASSERT_EQ(target2.addSound(s2), 1); // add existing Sound
+
+    ASSERT_EQ(target2.sounds().size(), 3);
+    ASSERT_EQ(target2.sounds()[0]->name(), s1->name());
+    ASSERT_EQ(target2.sounds()[1]->name(), s2->name());
+    ASSERT_EQ(target2.sounds()[2]->name(), s3->name());
+
+    ASSERT_EQ(target2.soundAt(0)->name(), s1->name());
+    ASSERT_EQ(target2.soundAt(1)->name(), s2->name());
+    ASSERT_EQ(target2.soundAt(2)->name(), s3->name());
+
+    ASSERT_EQ(target2.findSound("invalid"), -1);
+    ASSERT_EQ(target2.findSound("sound1"), 0);
+    ASSERT_EQ(target2.findSound("sound2"), 1);
+    ASSERT_EQ(target2.findSound("sound3"), 2);
+
+    ASSERT_EQ(target2.sounds(), source.sounds());
+
+    auto s4 = std::make_shared<Sound>("sound4", "", "wav");
+    ASSERT_EQ(source.addSound(s4), 3);
+
+    EXPECT_CALL(target2, dataSource()).WillOnce(Return(&source));
+    ASSERT_EQ(target2.sounds(), source.sounds());
+
+    ASSERT_EQ(source.sounds().size(), 4);
+    ASSERT_EQ(source.sounds()[0]->name(), s1->name());
+    ASSERT_EQ(source.sounds()[1]->name(), s2->name());
+    ASSERT_EQ(source.sounds()[2]->name(), s3->name());
+    ASSERT_EQ(source.sounds()[3]->name(), s4->name());
+
+    ASSERT_EQ(source.soundAt(0)->name(), s1->name());
+    ASSERT_EQ(source.soundAt(1)->name(), s2->name());
+    ASSERT_EQ(source.soundAt(2)->name(), s3->name());
+    ASSERT_EQ(source.soundAt(3)->name(), s4->name());
+
+    ASSERT_EQ(source.findSound("invalid"), -1);
+    ASSERT_EQ(source.findSound("sound1"), 0);
+    ASSERT_EQ(source.findSound("sound2"), 1);
+    ASSERT_EQ(source.findSound("sound3"), 2);
+    ASSERT_EQ(source.findSound("sound4"), 3);
 }
 
 TEST(TargetTest, LayerOrder)
@@ -205,3 +378,9 @@ TEST(TargetTest, Engine)
     target.setEngine(&engine);
     ASSERT_EQ(target.engine(), &engine);
 }
+
+TEST(TargetTest, DataSource)
+{
+    TargetMock target;
+    ASSERT_EQ(target.dataSourceBase(), nullptr);
+}

From 846043dce6be76b4dac9312002e70a24bdd3e66b Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sat, 2 Sep 2023 21:53:17 +0200
Subject: [PATCH 28/56] Use clone root sprite as the data source for blocks and
 assets

---
 include/scratchcpp/sprite.h          | 2 ++
 src/scratch/sprite.cpp               | 5 +++++
 test/scratch_classes/sprite_test.cpp | 8 ++++++++
 3 files changed, 15 insertions(+)

diff --git a/include/scratchcpp/sprite.h b/include/scratchcpp/sprite.h
index 9597bf90..dd50f56e 100644
--- a/include/scratchcpp/sprite.h
+++ b/include/scratchcpp/sprite.h
@@ -60,6 +60,8 @@ class LIBSCRATCHCPP_EXPORT Sprite : public Target
         void setRotationStyle(const char *newRotationStyle);
 
     private:
+        Target *dataSource() const override;
+
         spimpl::unique_impl_ptr<SpritePrivate> impl;
 };
 
diff --git a/src/scratch/sprite.cpp b/src/scratch/sprite.cpp
index 83437c8f..b7db560c 100644
--- a/src/scratch/sprite.cpp
+++ b/src/scratch/sprite.cpp
@@ -255,3 +255,8 @@ void Sprite::setRotationStyle(const char *newRotationStyle)
 {
     setRotationStyle(std::string(newRotationStyle));
 }
+
+Target *Sprite::dataSource() const
+{
+    return impl->cloneRoot;
+}
diff --git a/test/scratch_classes/sprite_test.cpp b/test/scratch_classes/sprite_test.cpp
index 1097fc7f..8abe2426 100644
--- a/test/scratch_classes/sprite_test.cpp
+++ b/test/scratch_classes/sprite_test.cpp
@@ -1,6 +1,7 @@
 #include <scratchcpp/sprite.h>
 #include <scratchcpp/variable.h>
 #include <scratchcpp/list.h>
+#include <scratchcpp/costume.h>
 #include <enginemock.h>
 
 #include "../common.h"
@@ -32,6 +33,8 @@ TEST(SpriteTest, Clone)
     auto var2 = std::make_shared<Variable>("b", "var2", "world");
     sprite.addVariable(var1);
     sprite.addVariable(var2);
+    auto c1 = std::make_shared<Costume>("costume1", "", "svg");
+    sprite.addCostume(c1);
 
     auto list1 = std::make_shared<List>("c", "list1");
     list1->push_back("item1");
@@ -173,6 +176,11 @@ TEST(SpriteTest, Clone)
     ASSERT_TRUE(clone3->allChildren().empty());
 
     ASSERT_TRUE(clone4->allChildren().empty());
+
+    ASSERT_EQ(clone2->costumes(), sprite.costumes());
+    auto c2 = std::make_shared<Costume>("costume2", "", "png");
+    clone2->addCostume(c2);
+    ASSERT_EQ(clone2->costumes(), sprite.costumes());
 }
 
 TEST(SpriteTest, X)

From 1e656e26759e4447cdd21e88456e6760a2176fab Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sun, 3 Sep 2023 10:46:13 +0200
Subject: [PATCH 29/56] Implement control_create_clone_of block

---
 src/blocks/controlblocks.cpp        |  65 +++++++++++++++
 src/blocks/controlblocks.h          |   7 +-
 test/blocks/control_blocks_test.cpp | 118 +++++++++++++++++++++++++++-
 3 files changed, 188 insertions(+), 2 deletions(-)

diff --git a/src/blocks/controlblocks.cpp b/src/blocks/controlblocks.cpp
index 27008793..50329389 100644
--- a/src/blocks/controlblocks.cpp
+++ b/src/blocks/controlblocks.cpp
@@ -2,7 +2,10 @@
 
 #include <scratchcpp/iengine.h>
 #include <scratchcpp/compiler.h>
+#include <scratchcpp/sprite.h>
+#include <scratchcpp/input.h>
 #include <scratchcpp/field.h>
+#include <scratchcpp/block.h>
 #include <cassert>
 
 #include "controlblocks.h"
@@ -27,6 +30,7 @@ void ControlBlocks::registerBlocks(IEngine *engine)
     engine->addCompileFunction(this, "control_stop", &compileStop);
     engine->addCompileFunction(this, "control_wait", &compileWait);
     engine->addCompileFunction(this, "control_wait_until", &compileWaitUntil);
+    engine->addCompileFunction(this, "control_create_clone_of", &compileCreateClone);
 
     // Inputs
     engine->addInput(this, "SUBSTACK", SUBSTACK);
@@ -35,6 +39,7 @@ void ControlBlocks::registerBlocks(IEngine *engine)
     engine->addInput(this, "CONDITION", CONDITION);
     engine->addInput(this, "DURATION", DURATION);
     engine->addInput(this, "VALUE", VALUE);
+    engine->addInput(this, "CLONE_OPTION", CLONE_OPTION);
 
     // Fields
     engine->addField(this, "STOP_OPTION", STOP_OPTION);
@@ -155,6 +160,27 @@ void ControlBlocks::compileWaitUntil(Compiler *compiler)
     compiler->addFunctionCall(&waitUntil);
 }
 
+void ControlBlocks::compileCreateClone(Compiler *compiler)
+{
+    Input *input = compiler->input(CLONE_OPTION);
+
+    if (input->type() != Input::Type::ObscuredShadow) {
+        assert(input->pointsToDropdownMenu());
+        std::string spriteName = input->selectedMenuItem();
+
+        if (spriteName == "_myself_")
+            compiler->addFunctionCall(&createCloneOfMyself);
+        else {
+            int index = compiler->engine()->findTarget(spriteName);
+            compiler->addConstValue(index);
+            compiler->addFunctionCall(&createCloneByIndex);
+        }
+    } else {
+        compiler->addInput(input);
+        compiler->addFunctionCall(&createClone);
+    }
+}
+
 unsigned int ControlBlocks::stopAll(VirtualMachine *vm)
 {
     vm->engine()->stop();
@@ -196,3 +222,42 @@ unsigned int ControlBlocks::waitUntil(VirtualMachine *vm)
     }
     return 1;
 }
+
+unsigned int ControlBlocks::createClone(VirtualMachine *vm)
+{
+    std::string spriteName = vm->getInput(0, 1)->toString();
+    Target *target;
+
+    if (spriteName == "_myself_")
+        target = vm->target();
+    else
+        target = vm->engine()->targetAt(vm->engine()->findTarget(spriteName));
+
+    Sprite *sprite = dynamic_cast<Sprite *>(target);
+
+    if (sprite)
+        sprite->clone();
+
+    return 1;
+}
+
+unsigned int ControlBlocks::createCloneByIndex(VirtualMachine *vm)
+{
+    Target *target = vm->engine()->targetAt(vm->getInput(0, 1)->toInt());
+    Sprite *sprite = dynamic_cast<Sprite *>(target);
+
+    if (sprite)
+        sprite->clone();
+
+    return 1;
+}
+
+unsigned int ControlBlocks::createCloneOfMyself(VirtualMachine *vm)
+{
+    Sprite *sprite = dynamic_cast<Sprite *>(vm->target());
+
+    if (sprite)
+        sprite->clone();
+
+    return 0;
+}
diff --git a/src/blocks/controlblocks.h b/src/blocks/controlblocks.h
index ec35efce..0c651d6a 100644
--- a/src/blocks/controlblocks.h
+++ b/src/blocks/controlblocks.h
@@ -23,7 +23,8 @@ class ControlBlocks : public IBlockSection
             TIMES,
             CONDITION,
             DURATION,
-            VALUE
+            VALUE,
+            CLONE_OPTION
         };
 
         enum Fields
@@ -53,12 +54,16 @@ class ControlBlocks : public IBlockSection
         static void compileStop(Compiler *compiler);
         static void compileWait(Compiler *compiler);
         static void compileWaitUntil(Compiler *compiler);
+        static void compileCreateClone(Compiler *compiler);
 
         static unsigned int stopAll(VirtualMachine *vm);
         static unsigned int stopOtherScriptsInSprite(VirtualMachine *vm);
         static unsigned int startWait(VirtualMachine *vm);
         static unsigned int wait(VirtualMachine *vm);
         static unsigned int waitUntil(VirtualMachine *vm);
+        static unsigned int createClone(VirtualMachine *vm);
+        static unsigned int createCloneByIndex(VirtualMachine *vm);
+        static unsigned int createCloneOfMyself(VirtualMachine *vm);
 
         static inline std::unordered_map<VirtualMachine *, std::pair<std::chrono::steady_clock::time_point, int>> m_timeMap;
 };
diff --git a/test/blocks/control_blocks_test.cpp b/test/blocks/control_blocks_test.cpp
index fa87f014..fd1fb199 100644
--- a/test/blocks/control_blocks_test.cpp
+++ b/test/blocks/control_blocks_test.cpp
@@ -3,10 +3,12 @@
 #include <scratchcpp/input.h>
 #include <scratchcpp/field.h>
 #include <scratchcpp/variable.h>
+#include <scratchcpp/sprite.h>
 #include <enginemock.h>
 
 #include "../common.h"
 #include "blocks/controlblocks.h"
+#include "blocks/operatorblocks.h"
 #include "engine/internal/engine.h"
 
 using namespace libscratchcpp;
@@ -25,6 +27,23 @@ class ControlBlocksTest : public testing::Test
         // For any control block
         std::shared_ptr<Block> createControlBlock(const std::string &id, const std::string &opcode) const { return std::make_shared<Block>(id, opcode); }
 
+        // For control_create_clone_of
+        std::shared_ptr<Block> createCloneBlock(const std::string &id, const std::string &spriteName, std::shared_ptr<Block> valueBlock = nullptr)
+        {
+            auto block = createControlBlock(id, "control_create_clone_of");
+
+            if (valueBlock)
+                addObscuredInput(block, "CLONE_OPTION", ControlBlocks::CLONE_OPTION, valueBlock);
+            else {
+                auto input = addNullInput(block, "CLONE_OPTION", ControlBlocks::CLONE_OPTION);
+                auto menu = createControlBlock(id + "_menu", "control_create_clone_of_menu");
+                input->setValueBlock(menu);
+                addDropdownField(menu, "CLONE_OPTION", static_cast<ControlBlocks::Fields>(-1), spriteName, static_cast<ControlBlocks::FieldValues>(-1));
+            }
+
+            return block;
+        }
+
         void addSubstackInput(std::shared_ptr<Block> block, const std::string &name, ControlBlocks::Inputs id, std::shared_ptr<Block> valueBlock) const
         {
             auto input = std::make_shared<Input>(name, Input::Type::NoShadow);
@@ -52,12 +71,14 @@ class ControlBlocksTest : public testing::Test
             block->updateInputMap();
         }
 
-        void addNullInput(std::shared_ptr<Block> block, const std::string &name, ControlBlocks::Inputs id) const
+        std::shared_ptr<Input> addNullInput(std::shared_ptr<Block> block, const std::string &name, ControlBlocks::Inputs id) const
         {
             auto input = std::make_shared<Input>(name, Input::Type::Shadow);
             input->setInputId(id);
             block->addInput(input);
             block->updateInputMap();
+
+            return input;
         }
 
         void addDropdownField(std::shared_ptr<Block> block, const std::string &name, ControlBlocks::Fields id, const std::string &value, ControlBlocks::FieldValues valueId) const
@@ -130,6 +151,7 @@ TEST_F(ControlBlocksTest, RegisterBlocks)
     EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "control_stop", &ControlBlocks::compileStop)).Times(1);
     EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "control_wait", &ControlBlocks::compileWait)).Times(1);
     EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "control_wait_until", &ControlBlocks::compileWaitUntil)).Times(1);
+    EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "control_create_clone_of", &ControlBlocks::compileCreateClone)).Times(1);
 
     // Inputs
     EXPECT_CALL(m_engineMock, addInput(m_section.get(), "SUBSTACK", ControlBlocks::SUBSTACK));
@@ -138,6 +160,7 @@ TEST_F(ControlBlocksTest, RegisterBlocks)
     EXPECT_CALL(m_engineMock, addInput(m_section.get(), "CONDITION", ControlBlocks::CONDITION));
     EXPECT_CALL(m_engineMock, addInput(m_section.get(), "DURATION", ControlBlocks::DURATION));
     EXPECT_CALL(m_engineMock, addInput(m_section.get(), "VALUE", ControlBlocks::VALUE));
+    EXPECT_CALL(m_engineMock, addInput(m_section.get(), "CLONE_OPTION", ControlBlocks::CLONE_OPTION));
 
     // Fields
     EXPECT_CALL(m_engineMock, addField(m_section.get(), "STOP_OPTION", ControlBlocks::STOP_OPTION));
@@ -810,3 +833,96 @@ TEST_F(ControlBlocksTest, WaitUntilImpl)
     ASSERT_EQ(vm.registerCount(), 0);
     ASSERT_TRUE(vm.atEnd());
 }
+
+TEST_F(ControlBlocksTest, CreateCloneOf)
+{
+    Compiler compiler(&m_engineMock);
+
+    // create clone of [Sprite1]
+    auto block1 = createCloneBlock("a", "Sprite1");
+
+    // create clone of [myself]
+    auto block2 = createCloneBlock("b", "_myself_");
+
+    // create clone of (join "" "")
+    auto joinBlock = std::make_shared<Block>("d", "operator_join");
+    joinBlock->setCompileFunction(&OperatorBlocks::compileJoin);
+    auto block3 = createCloneBlock("c", "", joinBlock);
+
+    EXPECT_CALL(m_engineMock, findTarget("Sprite1")).WillOnce(Return(4));
+    EXPECT_CALL(m_engineMock, functionIndex(&ControlBlocks::createCloneByIndex)).WillOnce(Return(0));
+    EXPECT_CALL(m_engineMock, functionIndex(&ControlBlocks::createCloneOfMyself)).WillOnce(Return(1));
+    EXPECT_CALL(m_engineMock, functionIndex(&ControlBlocks::createClone)).WillOnce(Return(2));
+
+    compiler.init();
+    compiler.setBlock(block1);
+    ControlBlocks::compileCreateClone(&compiler);
+    compiler.setBlock(block2);
+    ControlBlocks::compileCreateClone(&compiler);
+    compiler.setBlock(block3);
+    ControlBlocks::compileCreateClone(&compiler);
+    compiler.end();
+
+    ASSERT_EQ(
+        compiler.bytecode(),
+        std::vector<unsigned int>({ vm::OP_START, vm::OP_CONST, 0, vm::OP_EXEC, 0, vm::OP_EXEC, 1, vm::OP_NULL, vm::OP_NULL, vm::OP_STR_CONCAT, vm::OP_EXEC, 2, vm::OP_HALT }));
+    ASSERT_EQ(compiler.constValues().size(), 1);
+    ASSERT_EQ(compiler.constValues()[0].toDouble(), 4);
+    ASSERT_TRUE(compiler.variables().empty());
+    ASSERT_TRUE(compiler.lists().empty());
+}
+
+TEST_F(ControlBlocksTest, CreateCloneOfImpl)
+{
+    static unsigned int bytecode1[] = { vm::OP_START, vm::OP_CONST, 0, vm::OP_EXEC, 0, vm::OP_HALT };
+    static unsigned int bytecode2[] = { vm::OP_START, vm::OP_EXEC, 1, vm::OP_HALT };
+    static unsigned int bytecode3[] = { vm::OP_START, vm::OP_CONST, 1, vm::OP_EXEC, 2, vm::OP_HALT };
+    static unsigned int bytecode4[] = { vm::OP_START, vm::OP_CONST, 2, vm::OP_EXEC, 2, vm::OP_HALT };
+    static BlockFunc functions[] = { &ControlBlocks::createCloneByIndex, &ControlBlocks::createCloneOfMyself, &ControlBlocks::createClone };
+    static Value constValues[] = { 4, "Sprite1", "_myself_" };
+
+    Sprite sprite;
+    sprite.setEngine(&m_engineMock);
+
+    VirtualMachine vm(&sprite, &m_engineMock, nullptr);
+    vm.setFunctions(functions);
+    vm.setConstValues(constValues);
+
+    EXPECT_CALL(m_engineMock, targetAt(4)).WillOnce(Return(&sprite));
+    EXPECT_CALL(m_engineMock, initClone).Times(1);
+
+    vm.setBytecode(bytecode1);
+    vm.run();
+
+    ASSERT_EQ(vm.registerCount(), 0);
+    ASSERT_EQ(sprite.allChildren().size(), 1);
+
+    EXPECT_CALL(m_engineMock, initClone).Times(1);
+
+    vm.setBytecode(bytecode2);
+    vm.run();
+
+    ASSERT_EQ(vm.registerCount(), 0);
+    ASSERT_EQ(sprite.allChildren().size(), 2);
+    ASSERT_EQ(sprite.children(), sprite.allChildren());
+
+    EXPECT_CALL(m_engineMock, findTarget).WillOnce(Return(4));
+    EXPECT_CALL(m_engineMock, targetAt(4)).WillOnce(Return(&sprite));
+    EXPECT_CALL(m_engineMock, initClone).Times(1);
+
+    vm.setBytecode(bytecode3);
+    vm.run();
+
+    ASSERT_EQ(vm.registerCount(), 0);
+    ASSERT_EQ(sprite.allChildren().size(), 3);
+    ASSERT_EQ(sprite.children(), sprite.allChildren());
+
+    EXPECT_CALL(m_engineMock, initClone).Times(1);
+
+    vm.setBytecode(bytecode4);
+    vm.run();
+
+    ASSERT_EQ(vm.registerCount(), 0);
+    ASSERT_EQ(sprite.allChildren().size(), 4);
+    ASSERT_EQ(sprite.children(), sprite.allChildren());
+}

From 1cf022c07231a0fbf8761d0b892df1bddc15f1eb Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sun, 3 Sep 2023 11:00:43 +0200
Subject: [PATCH 30/56] Implement control_start_as_clone block

---
 src/blocks/controlblocks.cpp        |  6 ++++++
 src/blocks/controlblocks.h          |  1 +
 test/blocks/control_blocks_test.cpp | 12 ++++++++++++
 3 files changed, 19 insertions(+)

diff --git a/src/blocks/controlblocks.cpp b/src/blocks/controlblocks.cpp
index 50329389..4ca20f7f 100644
--- a/src/blocks/controlblocks.cpp
+++ b/src/blocks/controlblocks.cpp
@@ -30,6 +30,7 @@ void ControlBlocks::registerBlocks(IEngine *engine)
     engine->addCompileFunction(this, "control_stop", &compileStop);
     engine->addCompileFunction(this, "control_wait", &compileWait);
     engine->addCompileFunction(this, "control_wait_until", &compileWaitUntil);
+    engine->addCompileFunction(this, "control_start_as_clone", &compileStartAsClone);
     engine->addCompileFunction(this, "control_create_clone_of", &compileCreateClone);
 
     // Inputs
@@ -160,6 +161,11 @@ void ControlBlocks::compileWaitUntil(Compiler *compiler)
     compiler->addFunctionCall(&waitUntil);
 }
 
+void ControlBlocks::compileStartAsClone(Compiler *compiler)
+{
+    compiler->engine()->addCloneInitScript(compiler->block());
+}
+
 void ControlBlocks::compileCreateClone(Compiler *compiler)
 {
     Input *input = compiler->input(CLONE_OPTION);
diff --git a/src/blocks/controlblocks.h b/src/blocks/controlblocks.h
index 0c651d6a..cc215663 100644
--- a/src/blocks/controlblocks.h
+++ b/src/blocks/controlblocks.h
@@ -54,6 +54,7 @@ class ControlBlocks : public IBlockSection
         static void compileStop(Compiler *compiler);
         static void compileWait(Compiler *compiler);
         static void compileWaitUntil(Compiler *compiler);
+        static void compileStartAsClone(Compiler *compiler);
         static void compileCreateClone(Compiler *compiler);
 
         static unsigned int stopAll(VirtualMachine *vm);
diff --git a/test/blocks/control_blocks_test.cpp b/test/blocks/control_blocks_test.cpp
index fd1fb199..39c44f3d 100644
--- a/test/blocks/control_blocks_test.cpp
+++ b/test/blocks/control_blocks_test.cpp
@@ -151,6 +151,7 @@ TEST_F(ControlBlocksTest, RegisterBlocks)
     EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "control_stop", &ControlBlocks::compileStop)).Times(1);
     EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "control_wait", &ControlBlocks::compileWait)).Times(1);
     EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "control_wait_until", &ControlBlocks::compileWaitUntil)).Times(1);
+    EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "control_start_as_clone", &ControlBlocks::compileStartAsClone)).Times(1);
     EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "control_create_clone_of", &ControlBlocks::compileCreateClone)).Times(1);
 
     // Inputs
@@ -926,3 +927,14 @@ TEST_F(ControlBlocksTest, CreateCloneOfImpl)
     ASSERT_EQ(sprite.allChildren().size(), 4);
     ASSERT_EQ(sprite.children(), sprite.allChildren());
 }
+
+TEST_F(ControlBlocksTest, StartAsClone)
+{
+    Compiler compiler(&m_engineMock);
+
+    auto block = createControlBlock("a", "control_start_as_clone");
+    compiler.setBlock(block);
+
+    EXPECT_CALL(m_engineMock, addCloneInitScript(block)).Times(1);
+    ControlBlocks::compileStartAsClone(&compiler);
+}

From 8da80bd63d49b4cb8152bfeab71abfca69ddf133 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sun, 3 Sep 2023 12:21:17 +0200
Subject: [PATCH 31/56] Target: Add virtual destructor

---
 include/scratchcpp/target.h | 1 +
 1 file changed, 1 insertion(+)

diff --git a/include/scratchcpp/target.h b/include/scratchcpp/target.h
index fe9e7e23..0cb991f5 100644
--- a/include/scratchcpp/target.h
+++ b/include/scratchcpp/target.h
@@ -24,6 +24,7 @@ class LIBSCRATCHCPP_EXPORT Target
     public:
         Target();
         Target(const Target &) = delete;
+        virtual ~Target() { }
 
         /*! Returns true if this Target is the stage. */
         virtual bool isStage() const { return false; }

From be30417eaf97fc74d7bbb5601189f705f250689d Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sun, 3 Sep 2023 12:21:44 +0200
Subject: [PATCH 32/56] Implement control_delete_this_clone block

---
 src/blocks/controlblocks.cpp        | 18 +++++++++++
 src/blocks/controlblocks.h          |  2 ++
 test/blocks/control_blocks_test.cpp | 47 +++++++++++++++++++++++++++++
 3 files changed, 67 insertions(+)

diff --git a/src/blocks/controlblocks.cpp b/src/blocks/controlblocks.cpp
index 4ca20f7f..9da72683 100644
--- a/src/blocks/controlblocks.cpp
+++ b/src/blocks/controlblocks.cpp
@@ -32,6 +32,7 @@ void ControlBlocks::registerBlocks(IEngine *engine)
     engine->addCompileFunction(this, "control_wait_until", &compileWaitUntil);
     engine->addCompileFunction(this, "control_start_as_clone", &compileStartAsClone);
     engine->addCompileFunction(this, "control_create_clone_of", &compileCreateClone);
+    engine->addCompileFunction(this, "control_delete_this_clone", &compileDeleteThisClone);
 
     // Inputs
     engine->addInput(this, "SUBSTACK", SUBSTACK);
@@ -187,6 +188,11 @@ void ControlBlocks::compileCreateClone(Compiler *compiler)
     }
 }
 
+void ControlBlocks::compileDeleteThisClone(Compiler *compiler)
+{
+    compiler->addFunctionCall(&deleteThisClone);
+}
+
 unsigned int ControlBlocks::stopAll(VirtualMachine *vm)
 {
     vm->engine()->stop();
@@ -267,3 +273,15 @@ unsigned int ControlBlocks::createCloneOfMyself(VirtualMachine *vm)
 
     return 0;
 }
+
+unsigned int ControlBlocks::deleteThisClone(VirtualMachine *vm)
+{
+    Target *target = vm->target();
+
+    if (target) {
+        vm->engine()->stopTarget(target, nullptr);
+        target->~Target();
+    }
+
+    return 0;
+}
diff --git a/src/blocks/controlblocks.h b/src/blocks/controlblocks.h
index cc215663..92e63aaf 100644
--- a/src/blocks/controlblocks.h
+++ b/src/blocks/controlblocks.h
@@ -56,6 +56,7 @@ class ControlBlocks : public IBlockSection
         static void compileWaitUntil(Compiler *compiler);
         static void compileStartAsClone(Compiler *compiler);
         static void compileCreateClone(Compiler *compiler);
+        static void compileDeleteThisClone(Compiler *compiler);
 
         static unsigned int stopAll(VirtualMachine *vm);
         static unsigned int stopOtherScriptsInSprite(VirtualMachine *vm);
@@ -65,6 +66,7 @@ class ControlBlocks : public IBlockSection
         static unsigned int createClone(VirtualMachine *vm);
         static unsigned int createCloneByIndex(VirtualMachine *vm);
         static unsigned int createCloneOfMyself(VirtualMachine *vm);
+        static unsigned int deleteThisClone(VirtualMachine *vm);
 
         static inline std::unordered_map<VirtualMachine *, std::pair<std::chrono::steady_clock::time_point, int>> m_timeMap;
 };
diff --git a/test/blocks/control_blocks_test.cpp b/test/blocks/control_blocks_test.cpp
index 39c44f3d..dab49c20 100644
--- a/test/blocks/control_blocks_test.cpp
+++ b/test/blocks/control_blocks_test.cpp
@@ -14,6 +14,8 @@
 using namespace libscratchcpp;
 
 using ::testing::Return;
+using ::testing::_;
+using ::testing::SaveArg;
 
 class ControlBlocksTest : public testing::Test
 {
@@ -153,6 +155,7 @@ TEST_F(ControlBlocksTest, RegisterBlocks)
     EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "control_wait_until", &ControlBlocks::compileWaitUntil)).Times(1);
     EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "control_start_as_clone", &ControlBlocks::compileStartAsClone)).Times(1);
     EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "control_create_clone_of", &ControlBlocks::compileCreateClone)).Times(1);
+    EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "control_delete_this_clone", &ControlBlocks::compileDeleteThisClone)).Times(1);
 
     // Inputs
     EXPECT_CALL(m_engineMock, addInput(m_section.get(), "SUBSTACK", ControlBlocks::SUBSTACK));
@@ -938,3 +941,47 @@ TEST_F(ControlBlocksTest, StartAsClone)
     EXPECT_CALL(m_engineMock, addCloneInitScript(block)).Times(1);
     ControlBlocks::compileStartAsClone(&compiler);
 }
+
+TEST_F(ControlBlocksTest, DeleteThisClone)
+{
+    Compiler compiler(&m_engineMock);
+
+    auto block = createControlBlock("a", "control_delete_this_clone");
+
+    EXPECT_CALL(m_engineMock, functionIndex(&ControlBlocks::deleteThisClone)).WillOnce(Return(0));
+
+    compiler.init();
+    compiler.setBlock(block);
+    ControlBlocks::compileDeleteThisClone(&compiler);
+    compiler.end();
+
+    ASSERT_EQ(compiler.bytecode(), std::vector<unsigned int>({ vm::OP_START, vm::OP_EXEC, 0, vm::OP_HALT }));
+    ASSERT_TRUE(compiler.constValues().empty());
+    ASSERT_TRUE(compiler.variables().empty());
+    ASSERT_TRUE(compiler.lists().empty());
+}
+
+TEST_F(ControlBlocksTest, DeleteThisCloneImpl)
+{
+    static unsigned int bytecode[] = { vm::OP_START, vm::OP_EXEC, 0, vm::OP_HALT };
+    static BlockFunc functions[] = { &ControlBlocks::deleteThisClone };
+
+    Sprite sprite;
+    sprite.setEngine(&m_engineMock);
+
+    Sprite *clone;
+    EXPECT_CALL(m_engineMock, initClone(_)).WillOnce(SaveArg<0>(&clone));
+    sprite.clone();
+    ASSERT_TRUE(clone);
+
+    VirtualMachine vm(clone, &m_engineMock, nullptr);
+    vm.setFunctions(functions);
+
+    EXPECT_CALL(m_engineMock, stopTarget(clone, nullptr)).Times(1);
+
+    vm.setBytecode(bytecode);
+    vm.run();
+
+    ASSERT_EQ(vm.registerCount(), 0);
+    ASSERT_TRUE(sprite.children().empty());
+}

From 10c9d7e48f06d53ba7ec3c40c702678206015bbc Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sun, 3 Sep 2023 12:22:45 +0200
Subject: [PATCH 33/56] Engine: Avoid duplicate scripts in scripts to remove

---
 src/engine/internal/engine.cpp | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/src/engine/internal/engine.cpp b/src/engine/internal/engine.cpp
index 3572e4fa..bfaf690c 100644
--- a/src/engine/internal/engine.cpp
+++ b/src/engine/internal/engine.cpp
@@ -143,7 +143,8 @@ void Engine::frame()
                     }
                 }
 
-                m_scriptsToRemove.push_back(script.get());
+                if (std::find(m_scriptsToRemove.begin(), m_scriptsToRemove.end(), script.get()) == m_scriptsToRemove.end())
+                    m_scriptsToRemove.push_back(script.get());
             }
         } while (!script->atEnd() && !m_breakFrame);
     }
@@ -262,7 +263,8 @@ void Engine::broadcast(unsigned int index, VirtualMachine *sourceScript, bool wa
 void Engine::stopScript(VirtualMachine *vm)
 {
     assert(vm);
-    m_scriptsToRemove.push_back(vm);
+    if (std::find(m_scriptsToRemove.begin(), m_scriptsToRemove.end(), vm) == m_scriptsToRemove.end())
+        m_scriptsToRemove.push_back(vm);
 }
 
 void Engine::stopTarget(Target *target, VirtualMachine *exceptScript)

From f0436e24002876335e0f66a7eadedc3191989bea Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sun, 3 Sep 2023 12:25:47 +0200
Subject: [PATCH 34/56] Add test for clones

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

diff --git a/test/clones.sb3 b/test/clones.sb3
new file mode 100644
index 0000000000000000000000000000000000000000..2325078d3a955c61d8ff748dfac06388df8d2fb3
GIT binary patch
diff --git a/test/engine/engine_test.cpp b/test/engine/engine_test.cpp
index f15364c9..cd45c1d2 100644
--- a/test/engine/engine_test.cpp
+++ b/test/engine/engine_test.cpp
@@ -1,5 +1,6 @@
 #include <scratchcpp/broadcast.h>
 #include <scratchcpp/block.h>
+#include <scratchcpp/project.h>
 #include <scratchcpp/variable.h>
 #include <scratchcpp/list.h>
 
@@ -331,3 +332,48 @@ TEST(EngineTest, ListOwner)
     ASSERT_EQ(engine.listOwner(list3.get()), t3.get());
     ASSERT_EQ(engine.listOwner(list4.get()), t1.get());
 }
+
+TEST(EngineTest, Clones)
+{
+    Project p("clones.sb3");
+    ASSERT_TRUE(p.load());
+    p.run();
+
+    auto engine = p.engine();
+
+    Target *stage = engine->targetAt(engine->findTarget("Stage"));
+    ASSERT_TRUE(stage);
+
+    ASSERT_VAR(stage, "clone1");
+    ASSERT_EQ(GET_VAR(stage, "clone1")->value().toInt(), 1);
+    ASSERT_VAR(stage, "clone2");
+    ASSERT_EQ(GET_VAR(stage, "clone2")->value().toInt(), 1);
+    ASSERT_VAR(stage, "clone3");
+    ASSERT_EQ(GET_VAR(stage, "clone3")->value().toInt(), 1);
+    ASSERT_VAR(stage, "clone4");
+    ASSERT_EQ(GET_VAR(stage, "clone4")->value().toInt(), 1);
+    ASSERT_VAR(stage, "clone5");
+    ASSERT_EQ(GET_VAR(stage, "clone5")->value().toInt(), 110);
+    ASSERT_VAR(stage, "delete_passed");
+    ASSERT_TRUE(GET_VAR(stage, "delete_passed")->value().toBool());
+
+    ASSERT_LIST(stage, "log1");
+    auto list = GET_LIST(stage, "log1");
+
+    for (int i = 0; i < list->size(); i++) {
+        if (i < 10)
+            ASSERT_EQ((*list)[i].toInt(), 1);
+        else
+            ASSERT_EQ((*list)[i].toInt(), 2);
+    }
+
+    ASSERT_LIST(stage, "log2");
+    list = GET_LIST(stage, "log2");
+
+    for (int i = 0; i < list->size(); i++) {
+        if (i < 10)
+            ASSERT_EQ((*list)[i].toInt(), 1);
+        else
+            ASSERT_EQ((*list)[i].toString(), "1 2"); // TODO: Change this to "12" after #188 is fixed
+    }
+}

From d5ed24a41bd75b159dcd605822cf691654e5def4 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sun, 3 Sep 2023 13:08:41 +0200
Subject: [PATCH 35/56] Engine: Fix assert in initClone()

---
 src/engine/internal/engine.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/engine/internal/engine.cpp b/src/engine/internal/engine.cpp
index bfaf690c..b38bf80c 100644
--- a/src/engine/internal/engine.cpp
+++ b/src/engine/internal/engine.cpp
@@ -300,7 +300,7 @@ void Engine::initClone(Sprite *clone)
 #ifndef NDEBUG
         // Since we're initializing the clone, it shouldn't have any running scripts
         for (const auto script : m_runningScripts)
-            assert(script->target() != clone);
+            assert((script->target() != clone) || (std::find(m_scriptsToRemove.begin(), m_scriptsToRemove.end(), script.get()) != m_scriptsToRemove.end()));
 #endif
 
         for (auto script : scripts) {

From ead9140deadac013c3a8f7d22cd1ea189124520f Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sun, 3 Sep 2023 13:19:01 +0200
Subject: [PATCH 36/56] README: Mark control blocks and clones as finished

---
 README.md | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/README.md b/README.md
index e988dc68..4ad7d12a 100644
--- a/README.md
+++ b/README.md
@@ -121,7 +121,7 @@ int main(int argc, char **argv) {
 - [ ] Looks blocks
 - [ ] Sound blocks
 - [ ] Event blocks
-- [ ] Control blocks
+- [x] Control blocks
 - [ ] Sensing blocks
 - [x] Operator blocks
 - [x] Custom blocks
@@ -129,7 +129,7 @@ int main(int argc, char **argv) {
 - [x] Broadcasts
 - [x] Variables (monitors are not implemented yet)
 - [x] Lists (monitors are not implemented yet)
-- [ ] Clones
+- [x] Clones
 - [ ] Timer
 - [ ] API for monitors
 - [ ] Project metadata

From bc84aad3d307ee941285a767539a2fb30d87c469 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sun, 3 Sep 2023 13:35:30 +0200
Subject: [PATCH 37/56] Add onCloned method to sprite interface

---
 include/scratchcpp/ispritehandler.h           |  2 ++
 src/scratch/sprite.cpp                        |  3 +++
 test/mocks/spritehandlermock.h                |  2 ++
 .../target_interfaces/ispritehandler_test.cpp | 19 +++++++++++++++++++
 4 files changed, 26 insertions(+)

diff --git a/include/scratchcpp/ispritehandler.h b/include/scratchcpp/ispritehandler.h
index 609214b4..310abba8 100644
--- a/include/scratchcpp/ispritehandler.h
+++ b/include/scratchcpp/ispritehandler.h
@@ -16,6 +16,8 @@ class LIBSCRATCHCPP_EXPORT ISpriteHandler
 
         virtual void onSpriteChanged(Sprite *sprite) = 0;
 
+        virtual void onCloned(Sprite *clone) = 0;
+
         virtual void onCostumeChanged(const char *data) = 0;
 
         virtual void onVisibleChanged(bool visible) = 0;
diff --git a/src/scratch/sprite.cpp b/src/scratch/sprite.cpp
index b7db560c..dc7bc5a6 100644
--- a/src/scratch/sprite.cpp
+++ b/src/scratch/sprite.cpp
@@ -85,6 +85,9 @@ std::shared_ptr<Sprite> Sprite::clone()
         // Call "when I start as clone" scripts
         eng->initClone(clone.get());
 
+        if (impl->iface)
+            impl->iface->onCloned(clone.get());
+
         return clone;
     }
 
diff --git a/test/mocks/spritehandlermock.h b/test/mocks/spritehandlermock.h
index 5bd1e13b..72863305 100644
--- a/test/mocks/spritehandlermock.h
+++ b/test/mocks/spritehandlermock.h
@@ -10,6 +10,8 @@ class SpriteHandlerMock : public ISpriteHandler
     public:
         MOCK_METHOD(void, onSpriteChanged, (Sprite *), (override));
 
+        MOCK_METHOD(void, onCloned, (Sprite *), (override));
+
         MOCK_METHOD(void, onCostumeChanged, (const char *), (override));
 
         MOCK_METHOD(void, onVisibleChanged, (bool), (override));
diff --git a/test/target_interfaces/ispritehandler_test.cpp b/test/target_interfaces/ispritehandler_test.cpp
index 820c1647..75b69fc7 100644
--- a/test/target_interfaces/ispritehandler_test.cpp
+++ b/test/target_interfaces/ispritehandler_test.cpp
@@ -1,9 +1,14 @@
 #include <spritehandlermock.h>
+#include <enginemock.h>
 
 #include "../common.h"
 
 using namespace libscratchcpp;
 
+using ::testing::Return;
+using ::testing::_;
+using ::testing::SaveArg;
+
 class ISpriteHandlerTest : public testing::Test
 {
     public:
@@ -11,12 +16,26 @@ class ISpriteHandlerTest : public testing::Test
         {
             EXPECT_CALL(m_handler, onSpriteChanged(&m_sprite)).Times(1);
             m_sprite.setInterface(&m_handler);
+            m_sprite.setEngine(&m_engine);
         }
 
         Sprite m_sprite;
         SpriteHandlerMock m_handler;
+        EngineMock m_engine;
 };
 
+TEST_F(ISpriteHandlerTest, Clone)
+{
+    Sprite *clone;
+    Sprite *cloneArg;
+    EXPECT_CALL(m_engine, initClone(_)).WillOnce(SaveArg<0>(&clone));
+    EXPECT_CALL(m_handler, onCloned(_)).WillOnce(SaveArg<0>(&cloneArg));
+
+    m_sprite.clone();
+    ASSERT_TRUE(clone);
+    ASSERT_EQ(cloneArg, clone);
+}
+
 TEST_F(ISpriteHandlerTest, Visible)
 {
     EXPECT_CALL(m_handler, onVisibleChanged(false)).Times(1);

From 07d026d00da9df26234156bf5f9d6131e55173e5 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sat, 9 Sep 2023 12:22:27 +0200
Subject: [PATCH 38/56] Value: Implement operator!=

---
 include/scratchcpp/value.h | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/include/scratchcpp/value.h b/include/scratchcpp/value.h
index 22e48559..2c872e26 100644
--- a/include/scratchcpp/value.h
+++ b/include/scratchcpp/value.h
@@ -532,6 +532,8 @@ class LIBSCRATCHCPP_EXPORT Value
             return false;
         }
 
+        friend bool operator!=(const Value &v1, const Value &v2) { return !(v1 == v2); }
+
         friend bool operator>(const Value &v1, const Value &v2)
         {
             if ((static_cast<int>(v1.m_type) < 0) || (static_cast<int>(v2.m_type) < 0)) {

From 5d3bd463e08046e2a1dc394d5261387b68c23a73 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sat, 9 Sep 2023 13:58:14 +0200
Subject: [PATCH 39/56] Add Value equality operator test

---
 test/scratch_classes/value_test.cpp | 352 ++++++++++++++++++++++++++++
 1 file changed, 352 insertions(+)

diff --git a/test/scratch_classes/value_test.cpp b/test/scratch_classes/value_test.cpp
index ec5ece7b..24d7f33e 100644
--- a/test/scratch_classes/value_test.cpp
+++ b/test/scratch_classes/value_test.cpp
@@ -1629,3 +1629,355 @@ TEST(ValueTest, ModOperator)
     v = -150;
     ASSERT_EQ(v % "-Infinity", -150);
 }
+
+TEST(ValueTest, EqualityOperators)
+{
+    {
+        Value v1 = 10;
+        Value v2 = 10;
+        Value v3 = 8;
+
+        ASSERT_TRUE(v1 == v2);
+        ASSERT_FALSE(v1 != v2);
+
+        ASSERT_FALSE(v1 == v3);
+        ASSERT_TRUE(v1 != v3);
+
+        ASSERT_FALSE(v2 == v3);
+        ASSERT_TRUE(v2 != v3);
+    }
+
+    {
+        Value v1 = -4.25;
+        Value v2 = -4.25;
+        Value v3 = 5.312;
+
+        ASSERT_TRUE(v1 == v2);
+        ASSERT_FALSE(v1 != v2);
+
+        ASSERT_FALSE(v1 == v3);
+        ASSERT_TRUE(v1 != v3);
+
+        ASSERT_FALSE(v2 == v3);
+        ASSERT_TRUE(v2 != v3);
+    }
+
+    {
+        Value v1 = true;
+        Value v2 = true;
+        Value v3 = false;
+
+        ASSERT_TRUE(v1 == v2);
+        ASSERT_FALSE(v1 != v2);
+
+        ASSERT_FALSE(v1 == v3);
+        ASSERT_TRUE(v1 != v3);
+
+        ASSERT_FALSE(v2 == v3);
+        ASSERT_TRUE(v2 != v3);
+    }
+
+    {
+        Value v1 = "abC def";
+        Value v2 = "abC def";
+        Value v3 = "abc dEf";
+        Value v4 = "hello world";
+
+        ASSERT_TRUE(v1 == v2);
+        ASSERT_FALSE(v1 != v2);
+
+        ASSERT_TRUE(v1 == v3);
+        ASSERT_FALSE(v1 != v3);
+
+        ASSERT_TRUE(v2 == v3);
+        ASSERT_FALSE(v2 != v3);
+
+        ASSERT_FALSE(v1 == v4);
+        ASSERT_TRUE(v1 != v4);
+
+        ASSERT_FALSE(v2 == v4);
+        ASSERT_TRUE(v2 != v4);
+
+        ASSERT_FALSE(v3 == v4);
+        ASSERT_TRUE(v3 != v4);
+    }
+
+    {
+        Value v1(Value::SpecialValue::Infinity);
+        Value v2(Value::SpecialValue::Infinity);
+        Value v3(Value::SpecialValue::NegativeInfinity);
+        Value v4(Value::SpecialValue::NegativeInfinity);
+        Value v5(Value::SpecialValue::NaN);
+        Value v6(Value::SpecialValue::NaN);
+
+        ASSERT_TRUE(v1 == v2);
+        ASSERT_FALSE(v1 != v2);
+
+        ASSERT_TRUE(v3 == v4);
+        ASSERT_FALSE(v3 != v4);
+
+        // TODO: Enable this after #196 is fixed
+        /*ASSERT_TRUE(v5 == v6);
+        ASSERT_FALSE(v5 != v6);*/
+
+        ASSERT_FALSE(v1 == v3);
+        ASSERT_TRUE(v1 != v3);
+
+        ASSERT_FALSE(v1 == v5);
+        ASSERT_TRUE(v1 != v5);
+
+        ASSERT_FALSE(v3 == v5);
+        ASSERT_TRUE(v3 != v5);
+    }
+
+    {
+        Value v1 = 5.00;
+        Value v2 = 5;
+        Value v3 = 6;
+
+        ASSERT_TRUE(v1 == v2);
+        ASSERT_FALSE(v1 != v2);
+
+        ASSERT_FALSE(v1 == v3);
+        ASSERT_TRUE(v1 != v3);
+
+        ASSERT_FALSE(v2 == v3);
+        ASSERT_TRUE(v2 != v3);
+    }
+
+    {
+        Value v1 = 1;
+        Value v2 = true;
+        Value v3 = false;
+
+        ASSERT_TRUE(v1 == v2);
+        ASSERT_FALSE(v1 != v2);
+
+        ASSERT_FALSE(v1 == v3);
+        ASSERT_TRUE(v1 != v3);
+
+        ASSERT_FALSE(v2 == v3);
+        ASSERT_TRUE(v2 != v3);
+    }
+
+    {
+        Value v1 = 5.25;
+        Value v2 = "5.25";
+        Value v3 = "5.26";
+
+        ASSERT_TRUE(v1 == v2);
+        ASSERT_FALSE(v1 != v2);
+
+        ASSERT_FALSE(v1 == v3);
+        ASSERT_TRUE(v1 != v3);
+
+        ASSERT_FALSE(v2 == v3);
+        ASSERT_TRUE(v2 != v3);
+    }
+
+    {
+        Value v1 = 1;
+        Value v2 = true;
+        Value v3 = false;
+
+        ASSERT_TRUE(v1 == v2);
+        ASSERT_FALSE(v1 != v2);
+
+        ASSERT_FALSE(v1 == v3);
+        ASSERT_TRUE(v1 != v3);
+
+        ASSERT_FALSE(v2 == v3);
+        ASSERT_TRUE(v2 != v3);
+    }
+
+    {
+        Value v1 = 5;
+        Value v2 = 0;
+        Value v3(Value::SpecialValue::Infinity);
+        Value v4(Value::SpecialValue::NegativeInfinity);
+        Value v5(Value::SpecialValue::NaN);
+
+        ASSERT_FALSE(v1 == v3);
+        ASSERT_TRUE(v1 != v3);
+
+        ASSERT_FALSE(v1 == v4);
+        ASSERT_TRUE(v1 != v4);
+
+        ASSERT_FALSE(v1 == v5);
+        ASSERT_TRUE(v1 != v5);
+
+        // TODO: Enable this after #198 is fixed
+        /*ASSERT_FALSE(v2 == v5);
+        ASSERT_TRUE(v2 != v5);*/
+    }
+
+    {
+        Value v1 = true;
+        Value v2 = false;
+        Value v3 = "true";
+        Value v4 = "false";
+        Value v5 = "TRUE";
+        Value v6 = "FALSE";
+
+        ASSERT_TRUE(v1 == v3);
+        ASSERT_FALSE(v1 != v3);
+
+        ASSERT_TRUE(v2 == v4);
+        ASSERT_FALSE(v2 != v4);
+
+        ASSERT_FALSE(v1 == v2);
+        ASSERT_TRUE(v1 != v2);
+
+        ASSERT_FALSE(v2 == v3);
+        ASSERT_TRUE(v2 != v3);
+
+        ASSERT_TRUE(v1 == v5);
+        ASSERT_FALSE(v1 != v5);
+
+        ASSERT_TRUE(v2 == v6);
+        ASSERT_FALSE(v2 != v6);
+    }
+
+    {
+        Value v1 = true;
+        Value v2 = false;
+        Value v3(Value::SpecialValue::Infinity);
+        Value v4(Value::SpecialValue::NegativeInfinity);
+        Value v5(Value::SpecialValue::NaN);
+
+        ASSERT_FALSE(v1 == v3);
+        ASSERT_TRUE(v1 != v3);
+
+        ASSERT_FALSE(v1 == v4);
+        ASSERT_TRUE(v1 != v4);
+
+        ASSERT_FALSE(v1 == v5);
+        ASSERT_TRUE(v1 != v5);
+
+        ASSERT_FALSE(v2 == v5);
+        ASSERT_TRUE(v2 != v5);
+    }
+
+    {
+        Value v1 = "Infinity";
+        Value v2 = "infinity";
+        Value v3 = "-Infinity";
+        Value v4 = "-infinity";
+        Value v5 = "NaN";
+        Value v6 = "nan";
+        Value v7(Value::SpecialValue::Infinity);
+        Value v8(Value::SpecialValue::NegativeInfinity);
+        Value v9(Value::SpecialValue::NaN);
+
+        // Infinity
+        ASSERT_TRUE(v1 == v7);
+        ASSERT_FALSE(v1 != v7);
+
+        ASSERT_FALSE(v1 == v8);
+        ASSERT_TRUE(v1 != v8);
+
+        ASSERT_FALSE(v1 == v9);
+        ASSERT_TRUE(v1 != v9);
+
+        // infinity
+        ASSERT_TRUE(v2 == v7);
+        ASSERT_FALSE(v2 != v7);
+
+        ASSERT_FALSE(v2 == v8);
+        ASSERT_TRUE(v2 != v8);
+
+        ASSERT_FALSE(v2 == v9);
+        ASSERT_TRUE(v2 != v9);
+
+        // -Infinity
+        ASSERT_FALSE(v3 == v7);
+        ASSERT_TRUE(v3 != v7);
+
+        ASSERT_TRUE(v3 == v8);
+        ASSERT_FALSE(v3 != v8);
+
+        ASSERT_FALSE(v3 == v9);
+        ASSERT_TRUE(v3 != v9);
+
+        // -infinity
+        ASSERT_FALSE(v4 == v7);
+        ASSERT_TRUE(v4 != v7);
+
+        ASSERT_TRUE(v4 == v8);
+        ASSERT_FALSE(v4 != v8);
+
+        ASSERT_FALSE(v4 == v9);
+        ASSERT_TRUE(v4 != v9);
+
+        // NaN
+        ASSERT_FALSE(v5 == v7);
+        ASSERT_TRUE(v5 != v7);
+
+        ASSERT_FALSE(v5 == v8);
+        ASSERT_TRUE(v5 != v8);
+
+        // TODO: Enable this after #197 is fixed
+        /*ASSERT_TRUE(v5 == v9);
+        ASSERT_FALSE(v5 != v9);*/
+
+        // nan
+        ASSERT_FALSE(v6 == v7);
+        ASSERT_TRUE(v6 != v7);
+
+        ASSERT_FALSE(v6 == v8);
+        ASSERT_TRUE(v6 != v8);
+
+        ASSERT_TRUE(v6 == v9);
+        ASSERT_FALSE(v6 != v9);
+    }
+
+    {
+        Value v1 = "abc";
+        Value v2 = " ";
+        Value v3 = "";
+        Value v4 = "0";
+        Value v5(Value::SpecialValue::Infinity);
+        Value v6(Value::SpecialValue::NegativeInfinity);
+        Value v7(Value::SpecialValue::NaN);
+
+        // Infinity
+        ASSERT_FALSE(v1 == v5);
+        ASSERT_TRUE(v1 != v5);
+
+        ASSERT_FALSE(v2 == v5);
+        ASSERT_TRUE(v2 != v5);
+
+        ASSERT_FALSE(v3 == v5);
+        ASSERT_TRUE(v3 != v5);
+
+        ASSERT_FALSE(v4 == v5);
+        ASSERT_TRUE(v4 != v5);
+
+        // -Infinity
+        ASSERT_FALSE(v1 == v6);
+        ASSERT_TRUE(v1 != v6);
+
+        ASSERT_FALSE(v2 == v6);
+        ASSERT_TRUE(v2 != v6);
+
+        ASSERT_FALSE(v3 == v6);
+        ASSERT_TRUE(v3 != v6);
+
+        ASSERT_FALSE(v4 == v6);
+        ASSERT_TRUE(v4 != v6);
+
+        // NaN
+        ASSERT_FALSE(v1 == v7);
+        ASSERT_TRUE(v1 != v7);
+
+        ASSERT_FALSE(v2 == v7);
+        ASSERT_TRUE(v2 != v7);
+
+        ASSERT_FALSE(v3 == v7);
+        ASSERT_TRUE(v3 != v7);
+
+        // TODO: Enable this after #198 is fixed
+        /*ASSERT_FALSE(v4 == v7);
+        ASSERT_TRUE(v4 != v7);*/
+    }
+}

From 196af54cb3199f5b67d23aae71a1ed2023de4cbc Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sat, 9 Sep 2023 14:59:29 +0200
Subject: [PATCH 40/56] Fix a typo in OP_LIST_APPEND test

---
 test/virtual_machine/virtual_machine_test.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/test/virtual_machine/virtual_machine_test.cpp b/test/virtual_machine/virtual_machine_test.cpp
index bb692b03..a770957c 100644
--- a/test/virtual_machine/virtual_machine_test.cpp
+++ b/test/virtual_machine/virtual_machine_test.cpp
@@ -841,7 +841,7 @@ TEST(VirtualMachineTest, OP_LIST_APPEND)
     ASSERT_EQ(list1[0], 3.52);
 
     ASSERT_EQ(list2.size(), 1);
-    ASSERT_EQ(list2[1], "test");
+    ASSERT_EQ(list2[0], "test");
 
     ASSERT_EQ(vm.registerCount(), 0);
 }

From 585dd30f40d8c1e8e13327748f0c4dfbabd16ec7 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sat, 9 Sep 2023 14:59:49 +0200
Subject: [PATCH 41/56] Fix value comparison in load project test

---
 test/load_project/load_project_test.cpp | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/test/load_project/load_project_test.cpp b/test/load_project/load_project_test.cpp
index 4329b102..040aeb55 100644
--- a/test/load_project/load_project_test.cpp
+++ b/test/load_project/load_project_test.cpp
@@ -196,7 +196,7 @@ TEST(LoadProjectTest, LoadTestProject)
         ASSERT_EQ(prototype->argumentIds().size(), 2);
         ASSERT_EQ(prototype->argumentNames(), std::vector<std::string>({ "num or text", "bool" }));
         ASSERT_EQ(prototype->argumentTypes(), std::vector<BlockPrototype::ArgType>({ BlockPrototype::ArgType::StringNum, BlockPrototype::ArgType::Bool }));
-        ASSERT_EQ(prototype->argumentDefaults(), std::vector<Value>({ 0, false }));
+        ASSERT_EQ(prototype->argumentDefaults(), std::vector<Value>({ "", false }));
         ASSERT_EQ(prototype->procCode(), "custom block %s %b");
         ASSERT_INPUT(blockPrototype, prototype->argumentIds()[0]);
         auto argBlock = GET_INPUT(blockPrototype, prototype->argumentIds()[0])->valueBlock();

From 4c03062acb7e3b2c60ebf0cee6f8a650799073d2 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sat, 9 Sep 2023 14:59:59 +0200
Subject: [PATCH 42/56] fix #142: Compare strings before comparing numbers

---
 include/scratchcpp/value.h          |  6 +++---
 test/scratch_classes/value_test.cpp | 16 ++++++++++++++++
 2 files changed, 19 insertions(+), 3 deletions(-)

diff --git a/include/scratchcpp/value.h b/include/scratchcpp/value.h
index 2c872e26..d72b2f9e 100644
--- a/include/scratchcpp/value.h
+++ b/include/scratchcpp/value.h
@@ -520,12 +520,12 @@ class LIBSCRATCHCPP_EXPORT Value
                         }
                 }
             } else {
-                if (v1.isNumber() || v2.isNumber())
+                if (v1.isString() || v2.isString())
+                    return stringsEqual(v1.toUtf16(), v2.toUtf16());
+                else if (v1.isNumber() || v2.isNumber())
                     return v1.toDouble() == v2.toDouble();
                 else if (v1.isBool() || v2.isBool())
                     return ((v1.m_type != Type::NaN && v2.m_type != Type::NaN) && (v1.toBool() == v2.toBool()));
-                else if (v1.isString() || v2.isString())
-                    return stringsEqual(v1.toUtf16(), v2.toUtf16());
                 else
                     return false;
             }
diff --git a/test/scratch_classes/value_test.cpp b/test/scratch_classes/value_test.cpp
index 24d7f33e..54bfa2d6 100644
--- a/test/scratch_classes/value_test.cpp
+++ b/test/scratch_classes/value_test.cpp
@@ -1775,6 +1775,22 @@ TEST(ValueTest, EqualityOperators)
         ASSERT_TRUE(v2 != v3);
     }
 
+    {
+        Value v1 = 0;
+        Value v2 = "0";
+        Value v3 = "1";
+        Value v4 = "test";
+
+        ASSERT_TRUE(v1 == v2);
+        ASSERT_FALSE(v1 != v2);
+
+        ASSERT_FALSE(v1 == v3);
+        ASSERT_TRUE(v1 != v3);
+
+        ASSERT_FALSE(v1 == v4);
+        ASSERT_TRUE(v1 != v4);
+    }
+
     {
         Value v1 = 1;
         Value v2 = true;

From b1fca8427cb820cf42ee6767c18bbe374a36e09a Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sun, 10 Sep 2023 10:42:30 +0200
Subject: [PATCH 43/56] Add range checks to asset getters

---
 src/scratch/target.cpp               |  8 ++++++--
 test/scratch_classes/target_test.cpp | 20 ++++++++++++++++----
 2 files changed, 22 insertions(+), 6 deletions(-)

diff --git a/src/scratch/target.cpp b/src/scratch/target.cpp
index 05743f7e..ff2ddc14 100644
--- a/src/scratch/target.cpp
+++ b/src/scratch/target.cpp
@@ -236,7 +236,9 @@ std::shared_ptr<Costume> Target::costumeAt(int index) const
     if (Target *source = dataSource())
         return source->costumeAt(index);
 
-    // TODO: Add range check
+    if (index < 0 || index >= impl->costumes.size())
+        return nullptr;
+
     return impl->costumes[index];
 }
 
@@ -285,7 +287,9 @@ std::shared_ptr<Sound> Target::soundAt(int index) const
     if (Target *source = dataSource())
         return source->soundAt(index);
 
-    // TODO: Add range check
+    if (index < 0 || index >= impl->sounds.size())
+        return nullptr;
+
     return impl->sounds[index];
 }
 
diff --git a/test/scratch_classes/target_test.cpp b/test/scratch_classes/target_test.cpp
index 821a5ff4..52cc598e 100644
--- a/test/scratch_classes/target_test.cpp
+++ b/test/scratch_classes/target_test.cpp
@@ -190,7 +190,7 @@ TEST(TargetTest, Costumes)
     auto c3 = std::make_shared<Costume>("costume3", "", "svg");
 
     TargetMock target;
-    EXPECT_CALL(target, dataSource()).Times(15).WillRepeatedly(Return(nullptr));
+    EXPECT_CALL(target, dataSource()).Times(17).WillRepeatedly(Return(nullptr));
 
     ASSERT_EQ(target.addCostume(c1), 0);
     ASSERT_EQ(target.addCostume(c2), 1);
@@ -207,6 +207,8 @@ TEST(TargetTest, Costumes)
     ASSERT_EQ(target.costumeAt(0)->name(), c1->name());
     ASSERT_EQ(target.costumeAt(1)->name(), c2->name());
     ASSERT_EQ(target.costumeAt(2)->name(), c3->name());
+    ASSERT_EQ(target.costumeAt(3), nullptr);
+    ASSERT_EQ(target.costumeAt(-1), nullptr);
 
     ASSERT_EQ(target.findCostume("invalid"), -1);
     ASSERT_EQ(target.findCostume("costume1"), 0);
@@ -221,7 +223,7 @@ TEST(TargetTest, Costumes)
     ASSERT_TRUE(target.costumes().empty());
 
     TargetMock target2;
-    EXPECT_CALL(target2, dataSource()).Times(16).WillRepeatedly(Return(&source));
+    EXPECT_CALL(target2, dataSource()).Times(18).WillRepeatedly(Return(&source));
 
     ASSERT_EQ(target2.addCostume(c1), 0);
     ASSERT_EQ(target2.addCostume(c2), 1);
@@ -236,6 +238,8 @@ TEST(TargetTest, Costumes)
     ASSERT_EQ(target2.costumeAt(0)->name(), c1->name());
     ASSERT_EQ(target2.costumeAt(1)->name(), c2->name());
     ASSERT_EQ(target2.costumeAt(2)->name(), c3->name());
+    ASSERT_EQ(target2.costumeAt(3), nullptr);
+    ASSERT_EQ(target2.costumeAt(-1), nullptr);
 
     ASSERT_EQ(target2.findCostume("invalid"), -1);
     ASSERT_EQ(target2.findCostume("costume1"), 0);
@@ -260,6 +264,8 @@ TEST(TargetTest, Costumes)
     ASSERT_EQ(source.costumeAt(1)->name(), c2->name());
     ASSERT_EQ(source.costumeAt(2)->name(), c3->name());
     ASSERT_EQ(source.costumeAt(3)->name(), c4->name());
+    ASSERT_EQ(source.costumeAt(4), nullptr);
+    ASSERT_EQ(source.costumeAt(-1), nullptr);
 
     ASSERT_EQ(source.findCostume("invalid"), -1);
     ASSERT_EQ(source.findCostume("costume1"), 0);
@@ -275,7 +281,7 @@ TEST(TargetTest, Sounds)
     auto s3 = std::make_shared<Sound>("sound3", "", "mp3");
 
     TargetMock target;
-    EXPECT_CALL(target, dataSource()).Times(15).WillRepeatedly(Return(nullptr));
+    EXPECT_CALL(target, dataSource()).Times(17).WillRepeatedly(Return(nullptr));
 
     ASSERT_EQ(target.addSound(s1), 0);
     ASSERT_EQ(target.addSound(s2), 1);
@@ -292,6 +298,8 @@ TEST(TargetTest, Sounds)
     ASSERT_EQ(target.soundAt(0)->name(), s1->name());
     ASSERT_EQ(target.soundAt(1)->name(), s2->name());
     ASSERT_EQ(target.soundAt(2)->name(), s3->name());
+    ASSERT_EQ(target.soundAt(3), nullptr);
+    ASSERT_EQ(target.soundAt(-1), nullptr);
 
     ASSERT_EQ(target.findSound("invalid"), -1);
     ASSERT_EQ(target.findSound("sound1"), 0);
@@ -306,7 +314,7 @@ TEST(TargetTest, Sounds)
     ASSERT_TRUE(target.sounds().empty());
 
     TargetMock target2;
-    EXPECT_CALL(target2, dataSource()).Times(16).WillRepeatedly(Return(&source));
+    EXPECT_CALL(target2, dataSource()).Times(18).WillRepeatedly(Return(&source));
 
     ASSERT_EQ(target2.addSound(s1), 0);
     ASSERT_EQ(target2.addSound(s2), 1);
@@ -321,6 +329,8 @@ TEST(TargetTest, Sounds)
     ASSERT_EQ(target2.soundAt(0)->name(), s1->name());
     ASSERT_EQ(target2.soundAt(1)->name(), s2->name());
     ASSERT_EQ(target2.soundAt(2)->name(), s3->name());
+    ASSERT_EQ(target2.soundAt(3), nullptr);
+    ASSERT_EQ(target2.soundAt(-1), nullptr);
 
     ASSERT_EQ(target2.findSound("invalid"), -1);
     ASSERT_EQ(target2.findSound("sound1"), 0);
@@ -345,6 +355,8 @@ TEST(TargetTest, Sounds)
     ASSERT_EQ(source.soundAt(1)->name(), s2->name());
     ASSERT_EQ(source.soundAt(2)->name(), s3->name());
     ASSERT_EQ(source.soundAt(3)->name(), s4->name());
+    ASSERT_EQ(source.soundAt(4), nullptr);
+    ASSERT_EQ(source.soundAt(-1), nullptr);
 
     ASSERT_EQ(source.findSound("invalid"), -1);
     ASSERT_EQ(source.findSound("sound1"), 0);

From 26edb4030d1e5e879fc88c329bab009b987979a0 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sun, 10 Sep 2023 10:56:51 +0200
Subject: [PATCH 44/56] Update asset getter tests for shared_ptr

---
 test/scratch_classes/target_test.cpp | 90 ++++++++++------------------
 1 file changed, 30 insertions(+), 60 deletions(-)

diff --git a/test/scratch_classes/target_test.cpp b/test/scratch_classes/target_test.cpp
index 52cc598e..79790356 100644
--- a/test/scratch_classes/target_test.cpp
+++ b/test/scratch_classes/target_test.cpp
@@ -190,23 +190,17 @@ TEST(TargetTest, Costumes)
     auto c3 = std::make_shared<Costume>("costume3", "", "svg");
 
     TargetMock target;
-    EXPECT_CALL(target, dataSource()).Times(17).WillRepeatedly(Return(nullptr));
+    EXPECT_CALL(target, dataSource()).Times(14).WillRepeatedly(Return(nullptr));
 
     ASSERT_EQ(target.addCostume(c1), 0);
     ASSERT_EQ(target.addCostume(c2), 1);
     ASSERT_EQ(target.addCostume(c3), 2);
     ASSERT_EQ(target.addCostume(c2), 1); // add existing costume
 
-    // TODO: Use shared_ptr for assets
-    // ASSERT_EQ(target.costumes(), std::vector<std::shared_ptr<Costume>>({ c1, c2, c3 }));
-    ASSERT_EQ(target.costumes().size(), 3);
-    ASSERT_EQ(target.costumes()[0]->name(), c1->name());
-    ASSERT_EQ(target.costumes()[1]->name(), c2->name());
-    ASSERT_EQ(target.costumes()[2]->name(), c3->name());
-
-    ASSERT_EQ(target.costumeAt(0)->name(), c1->name());
-    ASSERT_EQ(target.costumeAt(1)->name(), c2->name());
-    ASSERT_EQ(target.costumeAt(2)->name(), c3->name());
+    ASSERT_EQ(target.costumes(), std::vector<std::shared_ptr<Costume>>({ c1, c2, c3 }));
+    ASSERT_EQ(target.costumeAt(0), c1);
+    ASSERT_EQ(target.costumeAt(1), c2);
+    ASSERT_EQ(target.costumeAt(2), c3);
     ASSERT_EQ(target.costumeAt(3), nullptr);
     ASSERT_EQ(target.costumeAt(-1), nullptr);
 
@@ -223,21 +217,17 @@ TEST(TargetTest, Costumes)
     ASSERT_TRUE(target.costumes().empty());
 
     TargetMock target2;
-    EXPECT_CALL(target2, dataSource()).Times(18).WillRepeatedly(Return(&source));
+    EXPECT_CALL(target2, dataSource()).Times(15).WillRepeatedly(Return(&source));
 
     ASSERT_EQ(target2.addCostume(c1), 0);
     ASSERT_EQ(target2.addCostume(c2), 1);
     ASSERT_EQ(target2.addCostume(c3), 2);
     ASSERT_EQ(target2.addCostume(c2), 1); // add existing costume
 
-    ASSERT_EQ(target2.costumes().size(), 3);
-    ASSERT_EQ(target2.costumes()[0]->name(), c1->name());
-    ASSERT_EQ(target2.costumes()[1]->name(), c2->name());
-    ASSERT_EQ(target2.costumes()[2]->name(), c3->name());
-
-    ASSERT_EQ(target2.costumeAt(0)->name(), c1->name());
-    ASSERT_EQ(target2.costumeAt(1)->name(), c2->name());
-    ASSERT_EQ(target2.costumeAt(2)->name(), c3->name());
+    ASSERT_EQ(target2.costumes(), std::vector<std::shared_ptr<Costume>>({ c1, c2, c3 }));
+    ASSERT_EQ(target2.costumeAt(0), c1);
+    ASSERT_EQ(target2.costumeAt(1), c2);
+    ASSERT_EQ(target2.costumeAt(2), c3);
     ASSERT_EQ(target2.costumeAt(3), nullptr);
     ASSERT_EQ(target2.costumeAt(-1), nullptr);
 
@@ -254,16 +244,11 @@ TEST(TargetTest, Costumes)
     EXPECT_CALL(target2, dataSource()).WillOnce(Return(&source));
     ASSERT_EQ(target2.costumes(), source.costumes());
 
-    ASSERT_EQ(source.costumes().size(), 4);
-    ASSERT_EQ(source.costumes()[0]->name(), c1->name());
-    ASSERT_EQ(source.costumes()[1]->name(), c2->name());
-    ASSERT_EQ(source.costumes()[2]->name(), c3->name());
-    ASSERT_EQ(source.costumes()[3]->name(), c4->name());
-
-    ASSERT_EQ(source.costumeAt(0)->name(), c1->name());
-    ASSERT_EQ(source.costumeAt(1)->name(), c2->name());
-    ASSERT_EQ(source.costumeAt(2)->name(), c3->name());
-    ASSERT_EQ(source.costumeAt(3)->name(), c4->name());
+    ASSERT_EQ(source.costumes(), std::vector<std::shared_ptr<Costume>>({ c1, c2, c3, c4 }));
+    ASSERT_EQ(source.costumeAt(0), c1);
+    ASSERT_EQ(source.costumeAt(1), c2);
+    ASSERT_EQ(source.costumeAt(2), c3);
+    ASSERT_EQ(source.costumeAt(3), c4);
     ASSERT_EQ(source.costumeAt(4), nullptr);
     ASSERT_EQ(source.costumeAt(-1), nullptr);
 
@@ -281,23 +266,17 @@ TEST(TargetTest, Sounds)
     auto s3 = std::make_shared<Sound>("sound3", "", "mp3");
 
     TargetMock target;
-    EXPECT_CALL(target, dataSource()).Times(17).WillRepeatedly(Return(nullptr));
+    EXPECT_CALL(target, dataSource()).Times(14).WillRepeatedly(Return(nullptr));
 
     ASSERT_EQ(target.addSound(s1), 0);
     ASSERT_EQ(target.addSound(s2), 1);
     ASSERT_EQ(target.addSound(s3), 2);
     ASSERT_EQ(target.addSound(s2), 1); // add existing Sound
 
-    // TODO: Use shared_ptr for assets
-    // ASSERT_EQ(target.sounds(), std::vector<std::shared_ptr<Sound>>({ s1, s2, s3 }));
-    ASSERT_EQ(target.sounds().size(), 3);
-    ASSERT_EQ(target.sounds()[0]->name(), s1->name());
-    ASSERT_EQ(target.sounds()[1]->name(), s2->name());
-    ASSERT_EQ(target.sounds()[2]->name(), s3->name());
-
-    ASSERT_EQ(target.soundAt(0)->name(), s1->name());
-    ASSERT_EQ(target.soundAt(1)->name(), s2->name());
-    ASSERT_EQ(target.soundAt(2)->name(), s3->name());
+    ASSERT_EQ(target.sounds(), std::vector<std::shared_ptr<Sound>>({ s1, s2, s3 }));
+    ASSERT_EQ(target.soundAt(0), s1);
+    ASSERT_EQ(target.soundAt(1), s2);
+    ASSERT_EQ(target.soundAt(2), s3);
     ASSERT_EQ(target.soundAt(3), nullptr);
     ASSERT_EQ(target.soundAt(-1), nullptr);
 
@@ -314,21 +293,17 @@ TEST(TargetTest, Sounds)
     ASSERT_TRUE(target.sounds().empty());
 
     TargetMock target2;
-    EXPECT_CALL(target2, dataSource()).Times(18).WillRepeatedly(Return(&source));
+    EXPECT_CALL(target2, dataSource()).Times(15).WillRepeatedly(Return(&source));
 
     ASSERT_EQ(target2.addSound(s1), 0);
     ASSERT_EQ(target2.addSound(s2), 1);
     ASSERT_EQ(target2.addSound(s3), 2);
     ASSERT_EQ(target2.addSound(s2), 1); // add existing Sound
 
-    ASSERT_EQ(target2.sounds().size(), 3);
-    ASSERT_EQ(target2.sounds()[0]->name(), s1->name());
-    ASSERT_EQ(target2.sounds()[1]->name(), s2->name());
-    ASSERT_EQ(target2.sounds()[2]->name(), s3->name());
-
-    ASSERT_EQ(target2.soundAt(0)->name(), s1->name());
-    ASSERT_EQ(target2.soundAt(1)->name(), s2->name());
-    ASSERT_EQ(target2.soundAt(2)->name(), s3->name());
+    ASSERT_EQ(target2.sounds(), std::vector<std::shared_ptr<Sound>>({ s1, s2, s3 }));
+    ASSERT_EQ(target2.soundAt(0), s1);
+    ASSERT_EQ(target2.soundAt(1), s2);
+    ASSERT_EQ(target2.soundAt(2), s3);
     ASSERT_EQ(target2.soundAt(3), nullptr);
     ASSERT_EQ(target2.soundAt(-1), nullptr);
 
@@ -345,16 +320,11 @@ TEST(TargetTest, Sounds)
     EXPECT_CALL(target2, dataSource()).WillOnce(Return(&source));
     ASSERT_EQ(target2.sounds(), source.sounds());
 
-    ASSERT_EQ(source.sounds().size(), 4);
-    ASSERT_EQ(source.sounds()[0]->name(), s1->name());
-    ASSERT_EQ(source.sounds()[1]->name(), s2->name());
-    ASSERT_EQ(source.sounds()[2]->name(), s3->name());
-    ASSERT_EQ(source.sounds()[3]->name(), s4->name());
-
-    ASSERT_EQ(source.soundAt(0)->name(), s1->name());
-    ASSERT_EQ(source.soundAt(1)->name(), s2->name());
-    ASSERT_EQ(source.soundAt(2)->name(), s3->name());
-    ASSERT_EQ(source.soundAt(3)->name(), s4->name());
+    ASSERT_EQ(source.sounds(), std::vector<std::shared_ptr<Sound>>({ s1, s2, s3, s4 }));
+    ASSERT_EQ(source.soundAt(0), s1);
+    ASSERT_EQ(source.soundAt(1), s2);
+    ASSERT_EQ(source.soundAt(2), s3);
+    ASSERT_EQ(source.soundAt(3), s4);
     ASSERT_EQ(source.soundAt(4), nullptr);
     ASSERT_EQ(source.soundAt(-1), nullptr);
 

From 4b4b47d673e0aa98a38dadb9efd3f708f6de4fc5 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sun, 10 Sep 2023 12:46:54 +0200
Subject: [PATCH 45/56] fix #146: Allow stopping "warp" scripts

---
 src/engine/virtualmachine.cpp   | 9 ++++-----
 src/engine/virtualmachine_p.cpp | 6 +++++-
 2 files changed, 9 insertions(+), 6 deletions(-)

diff --git a/src/engine/virtualmachine.cpp b/src/engine/virtualmachine.cpp
index c700c728..f4b2668a 100644
--- a/src/engine/virtualmachine.cpp
+++ b/src/engine/virtualmachine.cpp
@@ -190,15 +190,14 @@ void VirtualMachine::moveToLastCheckpoint()
  * \param[in] savePos Changes the return value of savePos().
  * \param[in] breakAtomic Whether to break the frame after stopping the script.
  * \param[in] goBack Whether to go back so that the current instruction can run again in the future.
- * \note Nothing will happen if the script is set to run without screen refresh.
+ * \note If the script is set to run without screen refresh, the VM won't stop.
+ * The only parameter which won't be ignored is goBack.
  */
 void VirtualMachine::stop(bool savePos, bool breakAtomic, bool goBack)
 {
-    if (impl->warp)
-        return;
     impl->stop = true;
-    impl->savePos = savePos;
-    impl->atomic = !breakAtomic;
+    impl->savePos = savePos && !impl->warp;
+    impl->atomic = !breakAtomic || impl->warp;
     impl->goBack = goBack;
 }
 
diff --git a/src/engine/virtualmachine_p.cpp b/src/engine/virtualmachine_p.cpp
index 9cc7d0c4..44468934 100644
--- a/src/engine/virtualmachine_p.cpp
+++ b/src/engine/virtualmachine_p.cpp
@@ -689,7 +689,11 @@ do_exec : {
             // This is for example used in the wait block (to call it again with the same time value).
         } else
             FREE_REGS(ret);
-        return pos;
+
+        if (!warp) // TODO: This should always return if there's a "warp timer" enabled
+            return pos;
+
+        DISPATCH(); // this avoids freeing registers after "stopping" a warp script
     }
     FREE_REGS(ret);
     DISPATCH();

From dce4c6fc48376669dbafe443fcff6dc6eb468daf Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sun, 10 Sep 2023 14:56:37 +0200
Subject: [PATCH 46/56] Set warp to false after all procedures finish

warp can be true only in procedures
---
 src/engine/virtualmachine_p.cpp | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/src/engine/virtualmachine_p.cpp b/src/engine/virtualmachine_p.cpp
index 44468934..2fb13ad6 100644
--- a/src/engine/virtualmachine_p.cpp
+++ b/src/engine/virtualmachine_p.cpp
@@ -201,6 +201,9 @@ unsigned int *VirtualMachinePrivate::run(unsigned int *pos, bool reset)
         atEnd = true;
         return pos;
     } else {
+        if (callTree.size() == 1)
+            warp = false;
+
         pos = callTree.back();
         callTree.pop_back();
         procedureArgTree.pop_back();

From 27fa0e8f532b85f0f2ca5be47609c4f38406ebd2 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Fri, 15 Sep 2023 23:33:11 +0200
Subject: [PATCH 47/56] Add Clock class

---
 src/engine/CMakeLists.txt     |  3 +++
 src/engine/internal/clock.cpp | 21 +++++++++++++++++++++
 src/engine/internal/clock.h   | 25 +++++++++++++++++++++++++
 src/engine/internal/iclock.h  | 18 ++++++++++++++++++
 test/CMakeLists.txt           |  1 +
 test/clock/CMakeLists.txt     | 12 ++++++++++++
 test/clock/clock_test.cpp     | 20 ++++++++++++++++++++
 test/mocks/clockmock.h        | 12 ++++++++++++
 8 files changed, 112 insertions(+)
 create mode 100644 src/engine/internal/clock.cpp
 create mode 100644 src/engine/internal/clock.h
 create mode 100644 src/engine/internal/iclock.h
 create mode 100644 test/clock/CMakeLists.txt
 create mode 100644 test/clock/clock_test.cpp
 create mode 100644 test/mocks/clockmock.h

diff --git a/src/engine/CMakeLists.txt b/src/engine/CMakeLists.txt
index 5984bcf9..8c8f171f 100644
--- a/src/engine/CMakeLists.txt
+++ b/src/engine/CMakeLists.txt
@@ -11,6 +11,9 @@ target_sources(scratchcpp
     script_p.h
     internal/engine.cpp
     internal/engine.h
+    internal/clock.cpp
+    internal/clock.h
+    internal/iclock.h
     internal/blocksectioncontainer.cpp
     internal/blocksectioncontainer.h
     internal/global.h
diff --git a/src/engine/internal/clock.cpp b/src/engine/internal/clock.cpp
new file mode 100644
index 00000000..29fe4a5b
--- /dev/null
+++ b/src/engine/internal/clock.cpp
@@ -0,0 +1,21 @@
+// SPDX-License-Identifier: Apache-2.0
+
+#include "clock.h"
+
+using namespace libscratchcpp;
+
+std::shared_ptr<Clock> Clock::m_instance = std::make_shared<Clock>();
+
+Clock::Clock()
+{
+}
+
+std::shared_ptr<Clock> Clock::instance()
+{
+    return m_instance;
+}
+
+std::chrono::steady_clock::time_point Clock::currentTime() const
+{
+    return std::chrono::steady_clock::now();
+}
diff --git a/src/engine/internal/clock.h b/src/engine/internal/clock.h
new file mode 100644
index 00000000..053b1723
--- /dev/null
+++ b/src/engine/internal/clock.h
@@ -0,0 +1,25 @@
+// SPDX-License-Identifier: Apache-2.0
+
+#pragma once
+
+#include <memory>
+#include "iclock.h"
+
+namespace libscratchcpp
+{
+
+class Clock : public IClock
+{
+    public:
+        Clock();
+        Clock(const Clock &) = delete;
+
+        static std::shared_ptr<Clock> instance();
+
+        std::chrono::steady_clock::time_point currentTime() const override;
+
+    private:
+        static std::shared_ptr<Clock> m_instance;
+};
+
+} // namespace libscratchcpp
diff --git a/src/engine/internal/iclock.h b/src/engine/internal/iclock.h
new file mode 100644
index 00000000..96b0bb55
--- /dev/null
+++ b/src/engine/internal/iclock.h
@@ -0,0 +1,18 @@
+// SPDX-License-Identifier: Apache-2.0
+
+#pragma once
+
+#include <chrono>
+
+namespace libscratchcpp
+{
+
+class IClock
+{
+    public:
+        virtual ~IClock() { }
+
+        virtual std::chrono::steady_clock::time_point currentTime() const = 0;
+};
+
+} // namespace libscratchcpp
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
index 385210a4..ff422796 100644
--- a/test/CMakeLists.txt
+++ b/test/CMakeLists.txt
@@ -28,3 +28,4 @@ add_subdirectory(assets)
 add_subdirectory(script)
 add_subdirectory(extensions)
 add_subdirectory(engine)
+add_subdirectory(clock)
diff --git a/test/clock/CMakeLists.txt b/test/clock/CMakeLists.txt
new file mode 100644
index 00000000..2e4f83a0
--- /dev/null
+++ b/test/clock/CMakeLists.txt
@@ -0,0 +1,12 @@
+add_executable(
+  clock_test
+  clock_test.cpp
+)
+
+target_link_libraries(
+  clock_test
+  GTest::gtest_main
+  scratchcpp
+)
+
+gtest_discover_tests(clock_test)
diff --git a/test/clock/clock_test.cpp b/test/clock/clock_test.cpp
new file mode 100644
index 00000000..a1bbc659
--- /dev/null
+++ b/test/clock/clock_test.cpp
@@ -0,0 +1,20 @@
+#include <engine/internal/clock.h>
+#include <cmath>
+
+#include "../common.h"
+
+using namespace libscratchcpp;
+
+TEST(ClockTest, CurrentTime)
+{
+    auto clock = Clock::instance();
+    ASSERT_TRUE(clock);
+
+    auto currentTime = std::chrono::time_point_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now());
+    double currentTimeMs = std::round(currentTime.time_since_epoch().count() / 10) * 10;
+
+    auto reportedTime = std::chrono::time_point_cast<std::chrono::milliseconds>(clock->currentTime());
+    double reportedTimeMs = std::round(reportedTime.time_since_epoch().count() / 10) * 10;
+
+    ASSERT_EQ(reportedTimeMs, currentTimeMs);
+}
diff --git a/test/mocks/clockmock.h b/test/mocks/clockmock.h
new file mode 100644
index 00000000..5e16bfa9
--- /dev/null
+++ b/test/mocks/clockmock.h
@@ -0,0 +1,12 @@
+#pragma once
+
+#include <engine/internal/iclock.h>
+#include <gmock/gmock.h>
+
+using namespace libscratchcpp;
+
+class ClockMock : public IClock
+{
+    public:
+        MOCK_METHOD(std::chrono::steady_clock::time_point, currentTime, (), (const override));
+};

From dc18ede25be525afb00e820263a2bf79a423f0d9 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Fri, 15 Sep 2023 23:35:23 +0200
Subject: [PATCH 48/56] Add Timer class

---
 CMakeLists.txt                |  1 +
 include/scratchcpp/itimer.h   | 25 +++++++++++++++++++++++
 src/engine/CMakeLists.txt     |  2 ++
 src/engine/internal/timer.cpp | 37 +++++++++++++++++++++++++++++++++++
 src/engine/internal/timer.h   | 30 ++++++++++++++++++++++++++++
 test/CMakeLists.txt           |  1 +
 test/mocks/timermock.h        | 14 +++++++++++++
 test/timer/CMakeLists.txt     | 14 +++++++++++++
 test/timer/timer_test.cpp     | 33 +++++++++++++++++++++++++++++++
 9 files changed, 157 insertions(+)
 create mode 100644 include/scratchcpp/itimer.h
 create mode 100644 src/engine/internal/timer.cpp
 create mode 100644 src/engine/internal/timer.h
 create mode 100644 test/mocks/timermock.h
 create mode 100644 test/timer/CMakeLists.txt
 create mode 100644 test/timer/timer_test.cpp

diff --git a/CMakeLists.txt b/CMakeLists.txt
index eb6b161f..bb155ab7 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -46,6 +46,7 @@ target_sources(scratchcpp
     include/scratchcpp/target.h
     include/scratchcpp/stage.h
     include/scratchcpp/sprite.h
+    include/scratchcpp/itimer.h
 )
 
 add_library(zip SHARED
diff --git a/include/scratchcpp/itimer.h b/include/scratchcpp/itimer.h
new file mode 100644
index 00000000..00c2ae9a
--- /dev/null
+++ b/include/scratchcpp/itimer.h
@@ -0,0 +1,25 @@
+// SPDX-License-Identifier: Apache-2.0
+
+#pragma once
+
+namespace libscratchcpp
+{
+
+/*!
+ * \brief The ITimer interface represents a timer of a Scratch project.
+ *
+ * You can get a project timer using Engine#timer().
+ */
+class ITimer
+{
+    public:
+        virtual ~ITimer() { }
+
+        /*! Returns the time since last reset in seconds. */
+        virtual double value() const = 0;
+
+        /*! Resets the timer. */
+        virtual void reset() = 0;
+};
+
+} // namespace libscratchcpp
diff --git a/src/engine/CMakeLists.txt b/src/engine/CMakeLists.txt
index 8c8f171f..6ab5078d 100644
--- a/src/engine/CMakeLists.txt
+++ b/src/engine/CMakeLists.txt
@@ -14,6 +14,8 @@ target_sources(scratchcpp
     internal/clock.cpp
     internal/clock.h
     internal/iclock.h
+    internal/timer.cpp
+    internal/timer.h
     internal/blocksectioncontainer.cpp
     internal/blocksectioncontainer.h
     internal/global.h
diff --git a/src/engine/internal/timer.cpp b/src/engine/internal/timer.cpp
new file mode 100644
index 00000000..53335558
--- /dev/null
+++ b/src/engine/internal/timer.cpp
@@ -0,0 +1,37 @@
+// SPDX-License-Identifier: Apache-2.0
+
+#include <cassert>
+
+#include "timer.h"
+#include "clock.h"
+
+using namespace libscratchcpp;
+
+Timer::Timer()
+{
+    m_clock = Clock::instance().get();
+    resetTimer();
+}
+
+Timer::Timer(IClock *clock) :
+    m_clock(clock)
+{
+    assert(clock);
+    resetTimer();
+}
+
+double Timer::value() const
+{
+    return std::chrono::duration_cast<std::chrono::milliseconds>(m_clock->currentTime() - m_startTime).count() / 1000.0;
+}
+
+void Timer::reset()
+{
+    resetTimer();
+}
+
+// Required to avoid calling the virtual method from the constructors
+void Timer::resetTimer()
+{
+    m_startTime = m_clock->currentTime();
+}
diff --git a/src/engine/internal/timer.h b/src/engine/internal/timer.h
new file mode 100644
index 00000000..7b3aed84
--- /dev/null
+++ b/src/engine/internal/timer.h
@@ -0,0 +1,30 @@
+// SPDX-License-Identifier: Apache-2.0
+
+#pragma once
+
+#include <scratchcpp/itimer.h>
+#include <chrono>
+
+namespace libscratchcpp
+{
+
+class IClock;
+
+class Timer : public ITimer
+{
+    public:
+        Timer();
+        Timer(IClock *clock);
+        Timer(const Timer &) = delete;
+
+        double value() const override;
+        void reset() override;
+
+    private:
+        void resetTimer();
+
+        std::chrono::steady_clock::time_point m_startTime;
+        IClock *m_clock = nullptr;
+};
+
+} // namespace libscratchcpp
diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt
index ff422796..efa66c18 100644
--- a/test/CMakeLists.txt
+++ b/test/CMakeLists.txt
@@ -29,3 +29,4 @@ add_subdirectory(script)
 add_subdirectory(extensions)
 add_subdirectory(engine)
 add_subdirectory(clock)
+add_subdirectory(timer)
diff --git a/test/mocks/timermock.h b/test/mocks/timermock.h
new file mode 100644
index 00000000..96cf5f8f
--- /dev/null
+++ b/test/mocks/timermock.h
@@ -0,0 +1,14 @@
+#pragma once
+
+#include <scratchcpp/itimer.h>
+#include <gmock/gmock.h>
+
+using namespace libscratchcpp;
+
+class TimerMock : public ITimer
+{
+    public:
+        MOCK_METHOD(double, value, (), (const, override));
+
+        MOCK_METHOD(void, reset, (), (override));
+};
diff --git a/test/timer/CMakeLists.txt b/test/timer/CMakeLists.txt
new file mode 100644
index 00000000..5041f24e
--- /dev/null
+++ b/test/timer/CMakeLists.txt
@@ -0,0 +1,14 @@
+add_executable(
+  timer_test
+  timer_test.cpp
+)
+
+target_link_libraries(
+  timer_test
+  GTest::gtest_main
+  GTest::gmock_main
+  scratchcpp
+  scratchcpp_mocks
+)
+
+gtest_discover_tests(timer_test)
diff --git a/test/timer/timer_test.cpp b/test/timer/timer_test.cpp
new file mode 100644
index 00000000..1daa0d33
--- /dev/null
+++ b/test/timer/timer_test.cpp
@@ -0,0 +1,33 @@
+#include <engine/internal/timer.h>
+#include <clockmock.h>
+
+#include "../common.h"
+
+using namespace libscratchcpp;
+
+using ::testing::Return;
+
+TEST(TimerTest, ValueAndReset)
+{
+    ClockMock clock;
+
+    std::chrono::steady_clock::time_point startTime(std::chrono::milliseconds(50));
+    EXPECT_CALL(clock, currentTime()).WillOnce(Return(startTime));
+    Timer timer(&clock);
+
+    std::chrono::steady_clock::time_point time1(std::chrono::milliseconds(73));
+    EXPECT_CALL(clock, currentTime()).WillOnce(Return(time1));
+    ASSERT_EQ(timer.value(), 0.023);
+
+    std::chrono::steady_clock::time_point time2(std::chrono::milliseconds(15432));
+    EXPECT_CALL(clock, currentTime()).WillOnce(Return(time2));
+    ASSERT_EQ(timer.value(), 15.382);
+
+    std::chrono::steady_clock::time_point time3(std::chrono::milliseconds(16025));
+    EXPECT_CALL(clock, currentTime()).WillOnce(Return(time3));
+    timer.reset();
+
+    std::chrono::steady_clock::time_point time4(std::chrono::milliseconds(24632));
+    EXPECT_CALL(clock, currentTime()).WillOnce(Return(time4));
+    ASSERT_EQ(timer.value(), 8.607);
+}

From 5c5eb05709cb6de03ea8b9c46296e2227456d17c Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sat, 16 Sep 2023 10:38:11 +0200
Subject: [PATCH 49/56] Add timer to Engine

---
 include/scratchcpp/iengine.h   |  4 ++++
 src/engine/internal/engine.cpp | 17 ++++++++++++++++-
 src/engine/internal/engine.h   |  7 +++++++
 test/engine/CMakeLists.txt     |  2 ++
 test/engine/engine_test.cpp    | 21 +++++++++++++++++++++
 test/mocks/enginemock.h        |  2 ++
 6 files changed, 52 insertions(+), 1 deletion(-)

diff --git a/include/scratchcpp/iengine.h b/include/scratchcpp/iengine.h
index c6d2312a..b622f665 100644
--- a/include/scratchcpp/iengine.h
+++ b/include/scratchcpp/iengine.h
@@ -19,6 +19,7 @@ class Sprite;
 class Variable;
 class List;
 class Script;
+class ITimer;
 
 /*!
  * \brief The IEngine interface provides an API for running Scratch projects.
@@ -106,6 +107,9 @@ class LIBSCRATCHCPP_EXPORT IEngine
         /*! Call this from a block implementation to ignore calls to skipFrame() until the current frame ends. */
         virtual void lockFrame() = 0;
 
+        /*! Returns the timer of the project. */
+        virtual ITimer *timer() const = 0;
+
         /*!
          * Registers the given block section.
          * \see <a href="blockSections.html">Block sections</a>
diff --git a/src/engine/internal/engine.cpp b/src/engine/internal/engine.cpp
index b38bf80c..135d928d 100644
--- a/src/engine/internal/engine.cpp
+++ b/src/engine/internal/engine.cpp
@@ -18,11 +18,14 @@
 
 #include "engine.h"
 #include "blocksectioncontainer.h"
+#include "timer.h"
 #include "../../blocks/standardblocks.h"
 
 using namespace libscratchcpp;
 
-Engine::Engine()
+Engine::Engine() :
+    m_defaultTimer(std::make_unique<Timer>()),
+    m_timer(m_defaultTimer.get())
 {
     srand(time(NULL));
 }
@@ -165,6 +168,8 @@ void Engine::frame()
 
 void Engine::start()
 {
+    m_timer->reset();
+
     for (auto target : m_targets) {
         auto gfBlocks = target->greenFlagBlocks();
         for (auto block : gfBlocks)
@@ -377,6 +382,16 @@ void Engine::lockFrame()
     m_lockFrame = true;
 }
 
+ITimer *Engine::timer() const
+{
+    return m_timer;
+}
+
+void Engine::setTimer(ITimer *timer)
+{
+    m_timer = timer;
+}
+
 void Engine::registerSection(std::shared_ptr<IBlockSection> section)
 {
     if (section) {
diff --git a/src/engine/internal/engine.h b/src/engine/internal/engine.h
index 06980bbc..04f519ba 100644
--- a/src/engine/internal/engine.h
+++ b/src/engine/internal/engine.h
@@ -4,6 +4,7 @@
 
 #include <scratchcpp/iengine.h>
 #include <scratchcpp/target.h>
+#include <scratchcpp/itimer.h>
 #include <unordered_map>
 #include <memory>
 #include <chrono>
@@ -43,6 +44,9 @@ class Engine : public IEngine
         void skipFrame() override;
         void lockFrame() override;
 
+        ITimer *timer() const override;
+        void setTimer(ITimer *timer);
+
         void registerSection(std::shared_ptr<IBlockSection> section) override;
         std::vector<std::shared_ptr<IBlockSection>> registeredSections() const;
         unsigned int functionIndex(BlockFunc f) override;
@@ -101,6 +105,9 @@ class Engine : public IEngine
         std::unordered_map<Variable *, Target *> m_variableOwners;
         std::unordered_map<List *, Target *> m_listOwners;
 
+        std::unique_ptr<ITimer> m_defaultTimer;
+        ITimer *m_timer = nullptr;
+
         bool m_breakFrame = false;
         bool m_skipFrame = false;
         bool m_lockFrame = false;
diff --git a/test/engine/CMakeLists.txt b/test/engine/CMakeLists.txt
index ea2440ac..b8d97c5c 100644
--- a/test/engine/CMakeLists.txt
+++ b/test/engine/CMakeLists.txt
@@ -8,7 +8,9 @@ add_executable(
 target_link_libraries(
   engine_test
   GTest::gtest_main
+  GTest::gmock_main
   scratchcpp
+  scratchcpp_mocks
 )
 
 gtest_discover_tests(engine_test)
diff --git a/test/engine/engine_test.cpp b/test/engine/engine_test.cpp
index cd45c1d2..12620f4a 100644
--- a/test/engine/engine_test.cpp
+++ b/test/engine/engine_test.cpp
@@ -3,6 +3,7 @@
 #include <scratchcpp/project.h>
 #include <scratchcpp/variable.h>
 #include <scratchcpp/list.h>
+#include <timermock.h>
 
 #include "../common.h"
 #include "testsection.h"
@@ -42,6 +43,26 @@ TEST(EngineTest, BreakFrame)
     ASSERT_TRUE(engine.breakingCurrentFrame());
 }
 
+TEST(EngineTest, Timer)
+{
+    Engine engine;
+    ASSERT_TRUE(engine.timer());
+    engine.timer()->reset(); // shouldn't crash
+
+    TimerMock timer;
+    engine.setTimer(&timer);
+    ASSERT_EQ(engine.timer(), &timer);
+
+    EXPECT_CALL(timer, reset()).Times(1);
+    engine.start();
+
+    EXPECT_CALL(timer, reset()).Times(0);
+    engine.stop();
+
+    EXPECT_CALL(timer, reset()).Times(1);
+    engine.run();
+}
+
 TEST(EngineTest, Sections)
 {
     Engine engine;
diff --git a/test/mocks/enginemock.h b/test/mocks/enginemock.h
index 99c20c4d..0beda8ec 100644
--- a/test/mocks/enginemock.h
+++ b/test/mocks/enginemock.h
@@ -32,6 +32,8 @@ class EngineMock : public IEngine
         MOCK_METHOD(void, skipFrame, (), (override));
         MOCK_METHOD(void, lockFrame, (), (override));
 
+        MOCK_METHOD(ITimer *, timer, (), (const, override));
+
         MOCK_METHOD(void, registerSection, (std::shared_ptr<IBlockSection>), (override));
         MOCK_METHOD(unsigned int, functionIndex, (BlockFunc), (override));
 

From 6442d11d2f43df9627ea2d11346956a3c3bba7d3 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sat, 16 Sep 2023 11:34:27 +0200
Subject: [PATCH 50/56] Add sensing blocks test

---
 test/blocks/CMakeLists.txt          | 16 ++++++++++++
 test/blocks/sensing_blocks_test.cpp | 38 +++++++++++++++++++++++++++++
 2 files changed, 54 insertions(+)
 create mode 100644 test/blocks/sensing_blocks_test.cpp

diff --git a/test/blocks/CMakeLists.txt b/test/blocks/CMakeLists.txt
index e5d915ca..9fe74ae2 100644
--- a/test/blocks/CMakeLists.txt
+++ b/test/blocks/CMakeLists.txt
@@ -93,3 +93,19 @@ target_link_libraries(
 )
 
 gtest_discover_tests(custom_blocks_test)
+
+# sensing_blocks_test
+add_executable(
+  sensing_blocks_test
+  sensing_blocks_test.cpp
+)
+
+target_link_libraries(
+  sensing_blocks_test
+  GTest::gtest_main
+  GTest::gmock_main
+  scratchcpp
+  scratchcpp_mocks
+)
+
+gtest_discover_tests(sensing_blocks_test)
diff --git a/test/blocks/sensing_blocks_test.cpp b/test/blocks/sensing_blocks_test.cpp
new file mode 100644
index 00000000..92951c89
--- /dev/null
+++ b/test/blocks/sensing_blocks_test.cpp
@@ -0,0 +1,38 @@
+#include <scratchcpp/compiler.h>
+#include <scratchcpp/block.h>
+#include <enginemock.h>
+
+#include "../common.h"
+#include "blocks/sensingblocks.h"
+#include "engine/internal/engine.h"
+
+using namespace libscratchcpp;
+
+class SensingBlocksTest : public testing::Test
+{
+    public:
+        void SetUp() override
+        {
+            m_section = std::make_unique<SensingBlocks>();
+            m_section->registerBlocks(&m_engine);
+        }
+
+        std::unique_ptr<IBlockSection> m_section;
+        EngineMock m_engineMock;
+        Engine m_engine;
+};
+
+TEST_F(SensingBlocksTest, Name)
+{
+    ASSERT_EQ(m_section->name(), "Sensing");
+}
+
+TEST_F(SensingBlocksTest, CategoryVisible)
+{
+    ASSERT_TRUE(m_section->categoryVisible());
+}
+
+TEST_F(SensingBlocksTest, RegisterBlocks)
+{
+    m_section->registerBlocks(&m_engineMock);
+}

From bd243210d0fce99590cbb0783c33cac2ce0a1aa5 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sat, 16 Sep 2023 11:58:37 +0200
Subject: [PATCH 51/56] Implement sensing_timer block

---
 src/blocks/sensingblocks.cpp        | 17 ++++++++++++
 src/blocks/sensingblocks.h          |  4 +++
 test/blocks/sensing_blocks_test.cpp | 41 +++++++++++++++++++++++++++++
 3 files changed, 62 insertions(+)

diff --git a/src/blocks/sensingblocks.cpp b/src/blocks/sensingblocks.cpp
index cfb4ed90..8a706fb3 100644
--- a/src/blocks/sensingblocks.cpp
+++ b/src/blocks/sensingblocks.cpp
@@ -1,5 +1,9 @@
 // SPDX-License-Identifier: Apache-2.0
 
+#include <scratchcpp/virtualmachine.h>
+#include <scratchcpp/compiler.h>
+#include <scratchcpp/iengine.h>
+#include <scratchcpp/itimer.h>
 #include "sensingblocks.h"
 
 using namespace libscratchcpp;
@@ -11,4 +15,17 @@ std::string SensingBlocks::name() const
 
 void SensingBlocks::registerBlocks(IEngine *engine)
 {
+    // Blocks
+    engine->addCompileFunction(this, "sensing_timer", &compileTimer);
+}
+
+void SensingBlocks::compileTimer(Compiler *compiler)
+{
+    compiler->addFunctionCall(&timer);
+}
+
+unsigned int SensingBlocks::timer(VirtualMachine *vm)
+{
+    vm->addReturnValue(vm->engine()->timer()->value());
+    return 0;
 }
diff --git a/src/blocks/sensingblocks.h b/src/blocks/sensingblocks.h
index 653f1b1b..8a8cd3f8 100644
--- a/src/blocks/sensingblocks.h
+++ b/src/blocks/sensingblocks.h
@@ -14,6 +14,10 @@ class SensingBlocks : public IBlockSection
         std::string name() const override;
 
         void registerBlocks(IEngine *engine) override;
+
+        static void compileTimer(Compiler *compiler);
+
+        static unsigned int timer(VirtualMachine *vm);
 };
 
 } // namespace libscratchcpp
diff --git a/test/blocks/sensing_blocks_test.cpp b/test/blocks/sensing_blocks_test.cpp
index 92951c89..0c1490c7 100644
--- a/test/blocks/sensing_blocks_test.cpp
+++ b/test/blocks/sensing_blocks_test.cpp
@@ -1,6 +1,7 @@
 #include <scratchcpp/compiler.h>
 #include <scratchcpp/block.h>
 #include <enginemock.h>
+#include <timermock.h>
 
 #include "../common.h"
 #include "blocks/sensingblocks.h"
@@ -8,6 +9,8 @@
 
 using namespace libscratchcpp;
 
+using ::testing::Return;
+
 class SensingBlocksTest : public testing::Test
 {
     public:
@@ -20,6 +23,7 @@ class SensingBlocksTest : public testing::Test
         std::unique_ptr<IBlockSection> m_section;
         EngineMock m_engineMock;
         Engine m_engine;
+        TimerMock m_timerMock;
 };
 
 TEST_F(SensingBlocksTest, Name)
@@ -34,5 +38,42 @@ TEST_F(SensingBlocksTest, CategoryVisible)
 
 TEST_F(SensingBlocksTest, RegisterBlocks)
 {
+    // Blocks
+    EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "sensing_timer", &SensingBlocks::compileTimer)).Times(1);
+
     m_section->registerBlocks(&m_engineMock);
 }
+
+TEST_F(SensingBlocksTest, Timer)
+{
+    Compiler compiler(&m_engineMock);
+
+    auto block = std::make_shared<Block>("a", "sensing_timer");
+
+    EXPECT_CALL(m_engineMock, functionIndex(&SensingBlocks::timer)).WillOnce(Return(0));
+
+    compiler.init();
+    compiler.setBlock(block);
+    SensingBlocks::compileTimer(&compiler);
+    compiler.end();
+
+    ASSERT_EQ(compiler.bytecode(), std::vector<unsigned int>({ vm::OP_START, vm::OP_EXEC, 0, vm::OP_HALT }));
+}
+
+TEST_F(SensingBlocksTest, TimerImpl)
+{
+    static unsigned int bytecode[] = { vm::OP_START, vm::OP_EXEC, 0, vm::OP_HALT };
+    static BlockFunc functions[] = { &SensingBlocks::timer };
+
+    VirtualMachine vm(nullptr, &m_engineMock, nullptr);
+    vm.setFunctions(functions);
+
+    EXPECT_CALL(m_engineMock, timer()).WillOnce(Return(&m_timerMock));
+    EXPECT_CALL(m_timerMock, value()).WillOnce(Return(2.375));
+
+    vm.setBytecode(bytecode);
+    vm.run();
+
+    ASSERT_EQ(vm.registerCount(), 1);
+    ASSERT_EQ(vm.getInput(0, 1)->toDouble(), 2.375);
+}

From cfab503b3621a9f7b15d8f2c6c87d825434a1069 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sat, 16 Sep 2023 12:06:21 +0200
Subject: [PATCH 52/56] Implement sensing_resettimer block

---
 src/blocks/sensingblocks.cpp        | 12 ++++++++++
 src/blocks/sensingblocks.h          |  2 ++
 test/blocks/sensing_blocks_test.cpp | 34 +++++++++++++++++++++++++++++
 3 files changed, 48 insertions(+)

diff --git a/src/blocks/sensingblocks.cpp b/src/blocks/sensingblocks.cpp
index 8a706fb3..2a0f0512 100644
--- a/src/blocks/sensingblocks.cpp
+++ b/src/blocks/sensingblocks.cpp
@@ -17,6 +17,7 @@ void SensingBlocks::registerBlocks(IEngine *engine)
 {
     // Blocks
     engine->addCompileFunction(this, "sensing_timer", &compileTimer);
+    engine->addCompileFunction(this, "sensing_resettimer", &compileResetTimer);
 }
 
 void SensingBlocks::compileTimer(Compiler *compiler)
@@ -24,8 +25,19 @@ void SensingBlocks::compileTimer(Compiler *compiler)
     compiler->addFunctionCall(&timer);
 }
 
+void SensingBlocks::compileResetTimer(Compiler *compiler)
+{
+    compiler->addFunctionCall(&resetTimer);
+}
+
 unsigned int SensingBlocks::timer(VirtualMachine *vm)
 {
     vm->addReturnValue(vm->engine()->timer()->value());
     return 0;
 }
+
+unsigned int SensingBlocks::resetTimer(VirtualMachine *vm)
+{
+    vm->engine()->timer()->reset();
+    return 0;
+}
diff --git a/src/blocks/sensingblocks.h b/src/blocks/sensingblocks.h
index 8a8cd3f8..c3a9d00d 100644
--- a/src/blocks/sensingblocks.h
+++ b/src/blocks/sensingblocks.h
@@ -16,8 +16,10 @@ class SensingBlocks : public IBlockSection
         void registerBlocks(IEngine *engine) override;
 
         static void compileTimer(Compiler *compiler);
+        static void compileResetTimer(Compiler *compiler);
 
         static unsigned int timer(VirtualMachine *vm);
+        static unsigned int resetTimer(VirtualMachine *vm);
 };
 
 } // namespace libscratchcpp
diff --git a/test/blocks/sensing_blocks_test.cpp b/test/blocks/sensing_blocks_test.cpp
index 0c1490c7..75b5f481 100644
--- a/test/blocks/sensing_blocks_test.cpp
+++ b/test/blocks/sensing_blocks_test.cpp
@@ -40,6 +40,7 @@ TEST_F(SensingBlocksTest, RegisterBlocks)
 {
     // Blocks
     EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "sensing_timer", &SensingBlocks::compileTimer)).Times(1);
+    EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "sensing_resettimer", &SensingBlocks::compileResetTimer)).Times(1);
 
     m_section->registerBlocks(&m_engineMock);
 }
@@ -77,3 +78,36 @@ TEST_F(SensingBlocksTest, TimerImpl)
     ASSERT_EQ(vm.registerCount(), 1);
     ASSERT_EQ(vm.getInput(0, 1)->toDouble(), 2.375);
 }
+
+TEST_F(SensingBlocksTest, ResetTimer)
+{
+    Compiler compiler(&m_engineMock);
+
+    auto block = std::make_shared<Block>("a", "sensing_resettimer");
+
+    EXPECT_CALL(m_engineMock, functionIndex(&SensingBlocks::resetTimer)).WillOnce(Return(0));
+
+    compiler.init();
+    compiler.setBlock(block);
+    SensingBlocks::compileResetTimer(&compiler);
+    compiler.end();
+
+    ASSERT_EQ(compiler.bytecode(), std::vector<unsigned int>({ vm::OP_START, vm::OP_EXEC, 0, vm::OP_HALT }));
+}
+
+TEST_F(SensingBlocksTest, ResetTimerImpl)
+{
+    static unsigned int bytecode[] = { vm::OP_START, vm::OP_EXEC, 0, vm::OP_HALT };
+    static BlockFunc functions[] = { &SensingBlocks::resetTimer };
+
+    VirtualMachine vm(nullptr, &m_engineMock, nullptr);
+    vm.setFunctions(functions);
+
+    EXPECT_CALL(m_engineMock, timer()).WillOnce(Return(&m_timerMock));
+    EXPECT_CALL(m_timerMock, reset()).Times(1);
+
+    vm.setBytecode(bytecode);
+    vm.run();
+
+    ASSERT_EQ(vm.registerCount(), 0);
+}

From ee52ef5f2979d42c311d180db22bcda696a99ca4 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sat, 16 Sep 2023 12:11:14 +0200
Subject: [PATCH 53/56] README: Mark timer as finished

---
 README.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/README.md b/README.md
index 4ad7d12a..368812fc 100644
--- a/README.md
+++ b/README.md
@@ -130,7 +130,7 @@ int main(int argc, char **argv) {
 - [x] Variables (monitors are not implemented yet)
 - [x] Lists (monitors are not implemented yet)
 - [x] Clones
-- [ ] Timer
+- [x] Timer
 - [ ] API for monitors
 - [ ] Project metadata
 - [ ] Turbo mode

From aba5f8e74821c7f25219d3b7606df2697f145073 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sat, 16 Sep 2023 14:12:08 +0200
Subject: [PATCH 54/56] Add system time to Clock

---
 src/engine/internal/clock.cpp |  7 ++++++-
 src/engine/internal/clock.h   |  3 ++-
 src/engine/internal/iclock.h  |  3 ++-
 src/engine/internal/timer.cpp |  4 ++--
 test/clock/clock_test.cpp     | 18 ++++++++++++++++--
 test/mocks/clockmock.h        |  3 ++-
 test/timer/timer_test.cpp     | 10 +++++-----
 7 files changed, 35 insertions(+), 13 deletions(-)

diff --git a/src/engine/internal/clock.cpp b/src/engine/internal/clock.cpp
index 29fe4a5b..9473dc48 100644
--- a/src/engine/internal/clock.cpp
+++ b/src/engine/internal/clock.cpp
@@ -15,7 +15,12 @@ std::shared_ptr<Clock> Clock::instance()
     return m_instance;
 }
 
-std::chrono::steady_clock::time_point Clock::currentTime() const
+std::chrono::steady_clock::time_point Clock::currentSteadyTime() const
 {
     return std::chrono::steady_clock::now();
 }
+
+std::chrono::system_clock::time_point Clock::currentSystemTime() const
+{
+    return std::chrono::system_clock::now();
+}
diff --git a/src/engine/internal/clock.h b/src/engine/internal/clock.h
index 053b1723..7ebf756c 100644
--- a/src/engine/internal/clock.h
+++ b/src/engine/internal/clock.h
@@ -16,7 +16,8 @@ class Clock : public IClock
 
         static std::shared_ptr<Clock> instance();
 
-        std::chrono::steady_clock::time_point currentTime() const override;
+        std::chrono::steady_clock::time_point currentSteadyTime() const override;
+        std::chrono::system_clock::time_point currentSystemTime() const override;
 
     private:
         static std::shared_ptr<Clock> m_instance;
diff --git a/src/engine/internal/iclock.h b/src/engine/internal/iclock.h
index 96b0bb55..90c55308 100644
--- a/src/engine/internal/iclock.h
+++ b/src/engine/internal/iclock.h
@@ -12,7 +12,8 @@ class IClock
     public:
         virtual ~IClock() { }
 
-        virtual std::chrono::steady_clock::time_point currentTime() const = 0;
+        virtual std::chrono::steady_clock::time_point currentSteadyTime() const = 0;
+        virtual std::chrono::system_clock::time_point currentSystemTime() const = 0;
 };
 
 } // namespace libscratchcpp
diff --git a/src/engine/internal/timer.cpp b/src/engine/internal/timer.cpp
index 53335558..f3ee7875 100644
--- a/src/engine/internal/timer.cpp
+++ b/src/engine/internal/timer.cpp
@@ -22,7 +22,7 @@ Timer::Timer(IClock *clock) :
 
 double Timer::value() const
 {
-    return std::chrono::duration_cast<std::chrono::milliseconds>(m_clock->currentTime() - m_startTime).count() / 1000.0;
+    return std::chrono::duration_cast<std::chrono::milliseconds>(m_clock->currentSteadyTime() - m_startTime).count() / 1000.0;
 }
 
 void Timer::reset()
@@ -33,5 +33,5 @@ void Timer::reset()
 // Required to avoid calling the virtual method from the constructors
 void Timer::resetTimer()
 {
-    m_startTime = m_clock->currentTime();
+    m_startTime = m_clock->currentSteadyTime();
 }
diff --git a/test/clock/clock_test.cpp b/test/clock/clock_test.cpp
index a1bbc659..c7cc0806 100644
--- a/test/clock/clock_test.cpp
+++ b/test/clock/clock_test.cpp
@@ -5,7 +5,7 @@
 
 using namespace libscratchcpp;
 
-TEST(ClockTest, CurrentTime)
+TEST(ClockTest, CurrentSteadyTime)
 {
     auto clock = Clock::instance();
     ASSERT_TRUE(clock);
@@ -13,7 +13,21 @@ TEST(ClockTest, CurrentTime)
     auto currentTime = std::chrono::time_point_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now());
     double currentTimeMs = std::round(currentTime.time_since_epoch().count() / 10) * 10;
 
-    auto reportedTime = std::chrono::time_point_cast<std::chrono::milliseconds>(clock->currentTime());
+    auto reportedTime = std::chrono::time_point_cast<std::chrono::milliseconds>(clock->currentSteadyTime());
+    double reportedTimeMs = std::round(reportedTime.time_since_epoch().count() / 10) * 10;
+
+    ASSERT_EQ(reportedTimeMs, currentTimeMs);
+}
+
+TEST(ClockTest, CurrentSystemTime)
+{
+    auto clock = Clock::instance();
+    ASSERT_TRUE(clock);
+
+    auto currentTime = std::chrono::time_point_cast<std::chrono::milliseconds>(std::chrono::system_clock::now());
+    double currentTimeMs = std::round(currentTime.time_since_epoch().count() / 10) * 10;
+
+    auto reportedTime = std::chrono::time_point_cast<std::chrono::milliseconds>(clock->currentSystemTime());
     double reportedTimeMs = std::round(reportedTime.time_since_epoch().count() / 10) * 10;
 
     ASSERT_EQ(reportedTimeMs, currentTimeMs);
diff --git a/test/mocks/clockmock.h b/test/mocks/clockmock.h
index 5e16bfa9..c8f8b1e3 100644
--- a/test/mocks/clockmock.h
+++ b/test/mocks/clockmock.h
@@ -8,5 +8,6 @@ using namespace libscratchcpp;
 class ClockMock : public IClock
 {
     public:
-        MOCK_METHOD(std::chrono::steady_clock::time_point, currentTime, (), (const override));
+        MOCK_METHOD(std::chrono::steady_clock::time_point, currentSteadyTime, (), (const, override));
+        MOCK_METHOD(std::chrono::system_clock::time_point, currentSystemTime, (), (const, override));
 };
diff --git a/test/timer/timer_test.cpp b/test/timer/timer_test.cpp
index 1daa0d33..cd862670 100644
--- a/test/timer/timer_test.cpp
+++ b/test/timer/timer_test.cpp
@@ -12,22 +12,22 @@ TEST(TimerTest, ValueAndReset)
     ClockMock clock;
 
     std::chrono::steady_clock::time_point startTime(std::chrono::milliseconds(50));
-    EXPECT_CALL(clock, currentTime()).WillOnce(Return(startTime));
+    EXPECT_CALL(clock, currentSteadyTime()).WillOnce(Return(startTime));
     Timer timer(&clock);
 
     std::chrono::steady_clock::time_point time1(std::chrono::milliseconds(73));
-    EXPECT_CALL(clock, currentTime()).WillOnce(Return(time1));
+    EXPECT_CALL(clock, currentSteadyTime()).WillOnce(Return(time1));
     ASSERT_EQ(timer.value(), 0.023);
 
     std::chrono::steady_clock::time_point time2(std::chrono::milliseconds(15432));
-    EXPECT_CALL(clock, currentTime()).WillOnce(Return(time2));
+    EXPECT_CALL(clock, currentSteadyTime()).WillOnce(Return(time2));
     ASSERT_EQ(timer.value(), 15.382);
 
     std::chrono::steady_clock::time_point time3(std::chrono::milliseconds(16025));
-    EXPECT_CALL(clock, currentTime()).WillOnce(Return(time3));
+    EXPECT_CALL(clock, currentSteadyTime()).WillOnce(Return(time3));
     timer.reset();
 
     std::chrono::steady_clock::time_point time4(std::chrono::milliseconds(24632));
-    EXPECT_CALL(clock, currentTime()).WillOnce(Return(time4));
+    EXPECT_CALL(clock, currentSteadyTime()).WillOnce(Return(time4));
     ASSERT_EQ(timer.value(), 8.607);
 }

From 151a485b1f93738e898829cf4d8e8f162a91ac5a Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sat, 16 Sep 2023 14:34:00 +0200
Subject: [PATCH 55/56] Implement sensing_dayssince2000 block

---
 src/blocks/sensingblocks.cpp        | 18 +++++++++++++
 src/blocks/sensingblocks.h          |  5 ++++
 test/blocks/sensing_blocks_test.cpp | 39 +++++++++++++++++++++++++++++
 3 files changed, 62 insertions(+)

diff --git a/src/blocks/sensingblocks.cpp b/src/blocks/sensingblocks.cpp
index 2a0f0512..24e2b056 100644
--- a/src/blocks/sensingblocks.cpp
+++ b/src/blocks/sensingblocks.cpp
@@ -6,8 +6,12 @@
 #include <scratchcpp/itimer.h>
 #include "sensingblocks.h"
 
+#include "../engine/internal/clock.h"
+
 using namespace libscratchcpp;
 
+IClock *SensingBlocks::clock = Clock::instance().get();
+
 std::string SensingBlocks::name() const
 {
     return "Sensing";
@@ -18,6 +22,7 @@ void SensingBlocks::registerBlocks(IEngine *engine)
     // Blocks
     engine->addCompileFunction(this, "sensing_timer", &compileTimer);
     engine->addCompileFunction(this, "sensing_resettimer", &compileResetTimer);
+    engine->addCompileFunction(this, "sensing_dayssince2000", &compileDaysSince2000);
 }
 
 void SensingBlocks::compileTimer(Compiler *compiler)
@@ -30,6 +35,11 @@ void SensingBlocks::compileResetTimer(Compiler *compiler)
     compiler->addFunctionCall(&resetTimer);
 }
 
+void SensingBlocks::compileDaysSince2000(Compiler *compiler)
+{
+    compiler->addFunctionCall(&daysSince2000);
+}
+
 unsigned int SensingBlocks::timer(VirtualMachine *vm)
 {
     vm->addReturnValue(vm->engine()->timer()->value());
@@ -41,3 +51,11 @@ unsigned int SensingBlocks::resetTimer(VirtualMachine *vm)
     vm->engine()->timer()->reset();
     return 0;
 }
+
+unsigned int SensingBlocks::daysSince2000(VirtualMachine *vm)
+{
+    auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(clock->currentSystemTime().time_since_epoch()).count();
+    vm->addReturnValue(ms / 86400000.0 - 10957);
+
+    return 0;
+}
diff --git a/src/blocks/sensingblocks.h b/src/blocks/sensingblocks.h
index c3a9d00d..6fcada74 100644
--- a/src/blocks/sensingblocks.h
+++ b/src/blocks/sensingblocks.h
@@ -3,6 +3,7 @@
 #pragma once
 
 #include <scratchcpp/iblocksection.h>
+#include "../engine/internal/clock.h"
 
 namespace libscratchcpp
 {
@@ -17,9 +18,13 @@ class SensingBlocks : public IBlockSection
 
         static void compileTimer(Compiler *compiler);
         static void compileResetTimer(Compiler *compiler);
+        static void compileDaysSince2000(Compiler *compiler);
 
         static unsigned int timer(VirtualMachine *vm);
         static unsigned int resetTimer(VirtualMachine *vm);
+        static unsigned int daysSince2000(VirtualMachine *vm);
+
+        static IClock *clock;
 };
 
 } // namespace libscratchcpp
diff --git a/test/blocks/sensing_blocks_test.cpp b/test/blocks/sensing_blocks_test.cpp
index 75b5f481..c0b93753 100644
--- a/test/blocks/sensing_blocks_test.cpp
+++ b/test/blocks/sensing_blocks_test.cpp
@@ -2,6 +2,7 @@
 #include <scratchcpp/block.h>
 #include <enginemock.h>
 #include <timermock.h>
+#include <clockmock.h>
 
 #include "../common.h"
 #include "blocks/sensingblocks.h"
@@ -24,6 +25,7 @@ class SensingBlocksTest : public testing::Test
         EngineMock m_engineMock;
         Engine m_engine;
         TimerMock m_timerMock;
+        ClockMock m_clockMock;
 };
 
 TEST_F(SensingBlocksTest, Name)
@@ -41,6 +43,7 @@ TEST_F(SensingBlocksTest, RegisterBlocks)
     // Blocks
     EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "sensing_timer", &SensingBlocks::compileTimer)).Times(1);
     EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "sensing_resettimer", &SensingBlocks::compileResetTimer)).Times(1);
+    EXPECT_CALL(m_engineMock, addCompileFunction(m_section.get(), "sensing_dayssince2000", &SensingBlocks::compileDaysSince2000)).Times(1);
 
     m_section->registerBlocks(&m_engineMock);
 }
@@ -111,3 +114,39 @@ TEST_F(SensingBlocksTest, ResetTimerImpl)
 
     ASSERT_EQ(vm.registerCount(), 0);
 }
+
+TEST_F(SensingBlocksTest, DaysSince2000)
+{
+    Compiler compiler(&m_engineMock);
+
+    auto block = std::make_shared<Block>("a", "sensing_dayssince2000");
+
+    EXPECT_CALL(m_engineMock, functionIndex(&SensingBlocks::daysSince2000)).WillOnce(Return(0));
+
+    compiler.init();
+    compiler.setBlock(block);
+    SensingBlocks::compileDaysSince2000(&compiler);
+    compiler.end();
+
+    ASSERT_EQ(compiler.bytecode(), std::vector<unsigned int>({ vm::OP_START, vm::OP_EXEC, 0, vm::OP_HALT }));
+}
+
+TEST_F(SensingBlocksTest, DaysSince2000Impl)
+{
+    static unsigned int bytecode[] = { vm::OP_START, vm::OP_EXEC, 0, vm::OP_HALT };
+    static BlockFunc functions[] = { &SensingBlocks::daysSince2000 };
+
+    VirtualMachine vm(nullptr, &m_engineMock, nullptr);
+    vm.setFunctions(functions);
+
+    std::chrono::system_clock::time_point time(std::chrono::milliseconds(1011243120562)); // Jan 17 2002 04:52:00
+    EXPECT_CALL(m_clockMock, currentSystemTime()).WillOnce(Return(time));
+
+    SensingBlocks::clock = &m_clockMock;
+    vm.setBytecode(bytecode);
+    vm.run();
+    SensingBlocks::clock = Clock::instance().get();
+
+    ASSERT_EQ(vm.registerCount(), 1);
+    ASSERT_EQ(vm.getInput(0, 1)->toDouble(), 747.20278428240817);
+}

From c7865ec5c978b763158bfc8321a10cb20821e092 Mon Sep 17 00:00:00 2001
From: adazem009 <68537469+adazem009@users.noreply.github.com>
Date: Sat, 16 Sep 2023 14:57:19 +0200
Subject: [PATCH 56/56] Rename Asset::md5ext() to Asset::fileName()

---
 include/scratchcpp/asset.h              | 2 +-
 src/scratch/asset.cpp                   | 2 +-
 test/assets/asset_test.cpp              | 4 ++--
 test/assets/costume_test.cpp            | 2 +-
 test/assets/sound_test.cpp              | 2 +-
 test/load_project/load_project_test.cpp | 6 +++---
 6 files changed, 9 insertions(+), 9 deletions(-)

diff --git a/include/scratchcpp/asset.h b/include/scratchcpp/asset.h
index ee6a1b43..98826f76 100644
--- a/include/scratchcpp/asset.h
+++ b/include/scratchcpp/asset.h
@@ -22,7 +22,7 @@ class LIBSCRATCHCPP_EXPORT Asset : public Entity
 
         const std::string &name() const;
 
-        const std::string &md5ext() const;
+        const std::string &fileName() const;
 
         const std::string &dataFormat() const;
 
diff --git a/src/scratch/asset.cpp b/src/scratch/asset.cpp
index a17977e0..7b000a94 100644
--- a/src/scratch/asset.cpp
+++ b/src/scratch/asset.cpp
@@ -28,7 +28,7 @@ const std::string &Asset::name() const
 }
 
 /*! Returns the name of the asset file. */
-const std::string &Asset::md5ext() const
+const std::string &Asset::fileName() const
 {
     return impl->fileName;
 }
diff --git a/test/assets/asset_test.cpp b/test/assets/asset_test.cpp
index 083a5674..7875b5b3 100644
--- a/test/assets/asset_test.cpp
+++ b/test/assets/asset_test.cpp
@@ -10,7 +10,7 @@ TEST(AssetTest, Constructors)
     ASSERT_EQ(asset.id(), "a");
     ASSERT_EQ(asset.name(), "costume1");
     ASSERT_EQ(asset.dataFormat(), "svg");
-    ASSERT_EQ(asset.md5ext(), "a.svg");
+    ASSERT_EQ(asset.fileName(), "a.svg");
 }
 
 TEST(AssetTest, Id)
@@ -19,5 +19,5 @@ TEST(AssetTest, Id)
 
     asset.setId("b");
     ASSERT_EQ(asset.id(), "b");
-    ASSERT_EQ(asset.md5ext(), "b.svg");
+    ASSERT_EQ(asset.fileName(), "b.svg");
 }
diff --git a/test/assets/costume_test.cpp b/test/assets/costume_test.cpp
index f180b43b..81fe327b 100644
--- a/test/assets/costume_test.cpp
+++ b/test/assets/costume_test.cpp
@@ -10,7 +10,7 @@ TEST(CostumeTest, Constructors)
     ASSERT_EQ(costume.id(), "a");
     ASSERT_EQ(costume.name(), "costume1");
     ASSERT_EQ(costume.dataFormat(), "svg");
-    ASSERT_EQ(costume.md5ext(), "a.svg");
+    ASSERT_EQ(costume.fileName(), "a.svg");
     ASSERT_EQ(costume.bitmapResolution(), 1);
     ASSERT_EQ(costume.rotationCenterX(), 0);
     ASSERT_EQ(costume.rotationCenterY(), 0);
diff --git a/test/assets/sound_test.cpp b/test/assets/sound_test.cpp
index 811af433..cddd50ff 100644
--- a/test/assets/sound_test.cpp
+++ b/test/assets/sound_test.cpp
@@ -10,7 +10,7 @@ TEST(SoundTest, Constructors)
     ASSERT_EQ(sound.id(), "a");
     ASSERT_EQ(sound.name(), "sound1");
     ASSERT_EQ(sound.dataFormat(), "svg");
-    ASSERT_EQ(sound.md5ext(), "a.svg");
+    ASSERT_EQ(sound.fileName(), "a.svg");
     ASSERT_EQ(sound.rate(), 0);
     ASSERT_EQ(sound.sampleCount(), 0);
 }
diff --git a/test/load_project/load_project_test.cpp b/test/load_project/load_project_test.cpp
index 040aeb55..a698883d 100644
--- a/test/load_project/load_project_test.cpp
+++ b/test/load_project/load_project_test.cpp
@@ -52,7 +52,7 @@ TEST(LoadProjectTest, EmptyProject)
         auto backdrop = stage->costumeAt(0);
         ASSERT_EQ(backdrop->name(), "backdrop1");
         ASSERT_FALSE(backdrop->id().empty());
-        ASSERT_EQ(backdrop->md5ext(), backdrop->id() + ".svg");
+        ASSERT_EQ(backdrop->fileName(), backdrop->id() + ".svg");
         ASSERT_EQ(backdrop->dataFormat(), "svg");
 
         i++;
@@ -111,7 +111,7 @@ TEST(LoadProjectTest, LoadTestProject)
         auto sound = sprite1->soundAt(0);
         ASSERT_EQ(sound->name(), "Meow");
         ASSERT_FALSE(sound->id().empty());
-        ASSERT_EQ(sound->md5ext(), sound->id() + ".wav");
+        ASSERT_EQ(sound->fileName(), sound->id() + ".wav");
         ASSERT_EQ(sound->dataFormat(), "wav");
 
         // Sprite1: variables
@@ -240,7 +240,7 @@ TEST(LoadProjectTest, LoadTestProject)
         sound = sprite2->soundAt(0);
         ASSERT_EQ(sound->name(), "Pop");
         ASSERT_FALSE(sound->id().empty());
-        ASSERT_EQ(sound->md5ext(), sound->id() + ".wav");
+        ASSERT_EQ(sound->fileName(), sound->id() + ".wav");
         ASSERT_EQ(sound->dataFormat(), "wav");
 
         // Balloon1: variables
