From 6aebf22158cc42401fbd97e1fdd9996e2f51a04e Mon Sep 17 00:00:00 2001
From: LilyMakesThings <127533508+LilyMakesThings@users.noreply.github.com>
Date: Sun, 11 Feb 2024 20:24:32 +0000
Subject: [PATCH 001/202] Text Dropdowns (vm Part) (#1)
Exposes the new textdropdowns to extensions.
To do: Find a logical way to implement number dropdowns.
---
src/engine/runtime.js | 11 ++++++-----
1 file changed, 6 insertions(+), 5 deletions(-)
diff --git a/src/engine/runtime.js b/src/engine/runtime.js
index 6fc794268f7..d9afa9b127d 100644
--- a/src/engine/runtime.js
+++ b/src/engine/runtime.js
@@ -1219,14 +1219,15 @@ class Runtime extends EventEmitter {
type: menuId,
inputsInline: true,
output: 'String',
- colour: categoryInfo.color1,
- colourSecondary: categoryInfo.color2,
- colourTertiary: categoryInfo.color3,
+ colour: menuInfo.acceptText ? '#FFFFFF' : categoryInfo.color1,
+ colourSecondary: menuInfo.acceptText ? '#FFFFFF' : categoryInfo.color2,
+ colourTertiary: menuInfo.acceptText ? '#FFFFFF' : categoryInfo.color3,
outputShape: menuInfo.acceptReporters ?
ScratchBlocksConstants.OUTPUT_SHAPE_ROUND : ScratchBlocksConstants.OUTPUT_SHAPE_SQUARE,
args0: [
- {
- type: 'field_dropdown',
+ {// to do: we could reimplement field_numberdropdown here really easily
+ type: menuInfo.acceptText ?
+ 'field_textdropdown' : 'field_dropdown',
name: menuName,
options: menuItems
}
From 398f0dcac8d4e4243ce745019174775a94cc1e28 Mon Sep 17 00:00:00 2001
From: LilyMakesThings <127533508+LilyMakesThings@users.noreply.github.com>
Date: Sun, 11 Feb 2024 20:26:59 +0000
Subject: [PATCH 002/202] Add BlockType.INLINE (#2)
Add the "Inline" BlockType for extensions.
Might add the Inline block as an official block in the distant future.
---
src/compiler/irgen.js | 4 ++--
src/engine/runtime.js | 5 +++++
src/extension-support/block-type.js | 8 +++++++-
3 files changed, 14 insertions(+), 3 deletions(-)
diff --git a/src/compiler/irgen.js b/src/compiler/irgen.js
index c3679fb41a7..880aaa82eaf 100644
--- a/src/compiler/irgen.js
+++ b/src/compiler/irgen.js
@@ -663,7 +663,7 @@ class ScriptTreeGenerator {
const blockInfo = this.getBlockInfo(block.opcode);
if (blockInfo) {
const type = blockInfo.info.blockType;
- if (type === BlockType.REPORTER || type === BlockType.BOOLEAN) {
+ if (type === BlockType.REPORTER || type === BlockType.BOOLEAN || type === BlockType.INLINE) {
return this.descendCompatLayer(block);
}
}
@@ -1415,7 +1415,7 @@ class ScriptTreeGenerator {
const blockInfo = this.getBlockInfo(block.opcode);
const blockType = (blockInfo && blockInfo.info && blockInfo.info.blockType) || BlockType.COMMAND;
const substacks = {};
- if (blockType === BlockType.CONDITIONAL || blockType === BlockType.LOOP) {
+ if (blockType === BlockType.CONDITIONAL || blockType === BlockType.LOOP || blockType === BlockType.INLINE) {
for (const inputName in block.inputs) {
if (!inputName.startsWith('SUBSTACK')) continue;
const branchNum = inputName === 'SUBSTACK' ? 1 : +inputName.substring('SUBSTACK'.length);
diff --git a/src/engine/runtime.js b/src/engine/runtime.js
index d9afa9b127d..76d42700857 100644
--- a/src/engine/runtime.js
+++ b/src/engine/runtime.js
@@ -1415,6 +1415,11 @@ class Runtime extends EventEmitter {
blockJSON.nextStatement = null; // null = available connection; undefined = terminal
}
break;
+ case BlockType.INLINE:
+ blockInfo.branchCount = blockInfo.branchCount || 1;
+ blockJSON.output = blockInfo.allowDropAnywhere ? null : 'String'; // TODO: distinguish number & string here?
+ blockJSON.outputShape = ScratchBlocksConstants.OUTPUT_SHAPE_SQUARE;
+ break;
}
const blockText = Array.isArray(blockInfo.text) ? blockInfo.text : [blockInfo.text];
diff --git a/src/extension-support/block-type.js b/src/extension-support/block-type.js
index ecaf70754dc..4680ef4af10 100644
--- a/src/extension-support/block-type.js
+++ b/src/extension-support/block-type.js
@@ -54,7 +54,13 @@ const BlockType = {
/**
* Arbitrary scratch-blocks XML.
*/
- XML: 'xml'
+ XML: 'xml',
+
+ /**
+ * Specialized reporter block that allows for the insertion and evaluation
+ * of a substack.
+ */
+ INLINE: 'inline'
};
module.exports = BlockType;
From fc02f89dbfd405ecb75302f2f3bf869f527ff6e0 Mon Sep 17 00:00:00 2001
From: LilyMakesThings <127533508+LilyMakesThings@users.noreply.github.com>
Date: Sun, 11 Feb 2024 20:47:13 +0000
Subject: [PATCH 003/202] "all at once" reimplementation (ScratchBlocks Part)
(#3)
Reimplements the deprecated "all at once" block. Changes a single comment.
---
src/blocks/scratch3_control.js | 12 ++++++------
src/compiler/jsgen.js | 7 +++++++
2 files changed, 13 insertions(+), 6 deletions(-)
diff --git a/src/blocks/scratch3_control.js b/src/blocks/scratch3_control.js
index ebf1951514e..7324c2d0266 100644
--- a/src/blocks/scratch3_control.js
+++ b/src/blocks/scratch3_control.js
@@ -193,13 +193,13 @@ class Scratch3ControlBlocks {
}
allAtOnce (args, util) {
- // Since the "all at once" block is implemented for compatiblity with
- // Scratch 2.0 projects, it behaves the same way it did in 2.0, which
- // is to simply run the contained script (like "if 1 = 1").
- // (In early versions of Scratch 2.0, it would work the same way as
- // "run without screen refresh" custom blocks do now, but this was
- // removed before the release of 2.0.)
+ // In Scratch 3.0 and TurboWarp, this would simply
+ // run the contained substack. In Unsandboxed,
+ // we've reimplemented the intended functionality
+ // of running the stack all in one frame.
+ util.thread.peekStackFrame().warpMode = false;
util.startBranch(1, false);
+ util.thread.peekStackFrame().warpMode = true;
}
}
diff --git a/src/compiler/jsgen.js b/src/compiler/jsgen.js
index ff07f3f0d57..b9a6c5e538b 100644
--- a/src/compiler/jsgen.js
+++ b/src/compiler/jsgen.js
@@ -876,6 +876,13 @@ class JSGenerator {
this.source += `}\n`;
break;
+ case 'control.allAtOnce':
+ const previousWarp = this.isWarp;
+ this.isWarp = true;
+ this.descendStack(node.code, new Frame(false, 'control.allAtOnce'));
+ this.isWarp = previousWarp;
+ break;
+
case 'counter.clear':
this.source += 'runtime.ext_scratch3_control._counter = 0;\n';
break;
From cd3bb5e74fcdd60897b8098afd185861dbbea7bd Mon Sep 17 00:00:00 2001
From: LilyMakesThings <127533508+LilyMakesThings@users.noreply.github.com>
Date: Mon, 12 Feb 2024 00:49:11 +0000
Subject: [PATCH 004/202] Fix allAtOnce in compiler.
---
src/compiler/irgen.js | 7 +++----
1 file changed, 3 insertions(+), 4 deletions(-)
diff --git a/src/compiler/irgen.js b/src/compiler/irgen.js
index 880aaa82eaf..d5c8e150400 100644
--- a/src/compiler/irgen.js
+++ b/src/compiler/irgen.js
@@ -694,15 +694,14 @@ class ScriptTreeGenerator {
descendStackedBlock (block) {
switch (block.opcode) {
case 'control_all_at_once':
- // In Scratch 3, this block behaves like "if 1 = 1"
+ // In Unsandboxed, attempts to run the script in 1 frame.
return {
- kind: 'control.if',
+ kind: 'control.allAtOnce',
condition: {
kind: 'constant',
value: true
},
- whenTrue: this.descendSubstack(block, 'SUBSTACK'),
- whenFalse: []
+ this.descendSubstack(block, 'SUBSTACK'),
};
case 'control_clear_counter':
return {
From 1b4127d824a75330713649d4939a3281ecc73a81 Mon Sep 17 00:00:00 2001
From: LilyMakesThings <127533508+LilyMakesThings@users.noreply.github.com>
Date: Mon, 12 Feb 2024 01:30:52 +0000
Subject: [PATCH 005/202] Fix allAtOnce in compiler (again)
---
src/compiler/irgen.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/compiler/irgen.js b/src/compiler/irgen.js
index d5c8e150400..294e051bbc8 100644
--- a/src/compiler/irgen.js
+++ b/src/compiler/irgen.js
@@ -701,7 +701,7 @@ class ScriptTreeGenerator {
kind: 'constant',
value: true
},
- this.descendSubstack(block, 'SUBSTACK'),
+ do: this.descendSubstack(block, 'SUBSTACK'),
};
case 'control_clear_counter':
return {
From 81def271a39a05c31fb7a2cfc95927c41b5d8dd4 Mon Sep 17 00:00:00 2001
From: LilyMakesThings <127533508+LilyMakesThings@users.noreply.github.com>
Date: Mon, 12 Feb 2024 01:37:37 +0000
Subject: [PATCH 006/202] Fix allAtOnce in compiler (for the final time)
---
src/compiler/irgen.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/compiler/irgen.js b/src/compiler/irgen.js
index 294e051bbc8..acd32ef2e04 100644
--- a/src/compiler/irgen.js
+++ b/src/compiler/irgen.js
@@ -701,7 +701,7 @@ class ScriptTreeGenerator {
kind: 'constant',
value: true
},
- do: this.descendSubstack(block, 'SUBSTACK'),
+ code: this.descendSubstack(block, 'SUBSTACK'),
};
case 'control_clear_counter':
return {
From 5b432b377679e2ee93239cca963bebf3e872d8a6 Mon Sep 17 00:00:00 2001
From: LilyMakesThings <127533508+LilyMakesThings@users.noreply.github.com>
Date: Mon, 12 Feb 2024 02:35:57 +0000
Subject: [PATCH 007/202] Update extension-manager.js (#4)
---
src/extension-support/extension-manager.js | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/src/extension-support/extension-manager.js b/src/extension-support/extension-manager.js
index 5ec3979e0ae..0f489120022 100644
--- a/src/extension-support/extension-manager.js
+++ b/src/extension-support/extension-manager.js
@@ -487,7 +487,8 @@ class ExtensionManager {
});
if (!menuItems || menuItems.length < 1) {
- throw new Error(`Extension menu returned no items: ${menuItemFunctionName}`);
+ console.warn(`Extension menu returned no items: ${menuItemFunctionName}`);
+ return [''];
}
return menuItems;
}
From 5a1d7c58529dcfccd9a0be6df3dc592396a698ea Mon Sep 17 00:00:00 2001
From: LilyMakesThings <127533508+LilyMakesThings@users.noreply.github.com>
Date: Mon, 12 Feb 2024 06:16:48 +0000
Subject: [PATCH 008/202] ArgumentType.VARIABLE (vm part) (#5)
The ScratchBlocks part will come later. For now, filter will go unused.
---
src/engine/runtime.js | 24 ++++++++++++++++++++++++
src/extension-support/argument-type.js | 7 ++++++-
2 files changed, 30 insertions(+), 1 deletion(-)
diff --git a/src/engine/runtime.js b/src/engine/runtime.js
index 76d42700857..a5ee3562c66 100644
--- a/src/engine/runtime.js
+++ b/src/engine/runtime.js
@@ -123,6 +123,10 @@ const ArgumentTypeMap = (() => {
fieldName: 'SOUND_MENU'
}
};
+ map[ArgumentType.VARIABLE] = {
+ fieldType: 'field_variable',
+ fieldName: 'VARIABLE'
+ };
return map;
})();
@@ -1589,6 +1593,24 @@ class Runtime extends EventEmitter {
};
}
+ /**
+ * Helper for _convertPlaceholdes which handles variable fields which are a specialized case of block "arguments".
+ * @param {object} argInfo Metadata about the inline image as specified by the extension
+ * @return {object} JSON blob for a scratch-blocks variable field.
+ * @private
+ */
+ _constructVariableJson (argInfo, placeholder) {
+ return {
+ type: 'field_variable',
+ name: placeholder,
+ variableTypes:
+ // eslint-disable-next-line max-len
+ argInfo.variableTypes ? (Array.isArray(argInfo.variableTypes) ? argInfo.variableTypes : [argInfo.variableTypes]) : [''],
+ variable: (argInfo.variableTypes === 'broadcast_msg') ? 'message1' : null,
+ filter: argInfo.filter ?? []
+ };
+ }
+
/**
* Helper for _convertForScratchBlocks which handles linearization of argument placeholders. Called as a callback
* from string#replace. In addition to the return value the JSON and XML items in the context will be filled.
@@ -1616,6 +1638,8 @@ class Runtime extends EventEmitter {
// check if this is not one of those cases. E.g. an inline image on a block.
if (argTypeInfo.fieldType === 'field_image') {
argJSON = this._constructInlineImageJson(argInfo);
+ } else if (argTypeInfo.fieldType === 'field_variable') {
+ argJSON = this._constructVariableJson(argInfo, placeholder);
} else {
// Construct input value
diff --git a/src/extension-support/argument-type.js b/src/extension-support/argument-type.js
index 2e8c13fff84..bf7fbdabb41 100644
--- a/src/extension-support/argument-type.js
+++ b/src/extension-support/argument-type.js
@@ -51,7 +51,12 @@ const ArgumentType = {
/**
* Name of sound in the current target
*/
- SOUND: 'sound'
+ SOUND: 'sound',
+
+ /**
+ * Name of variable in the current specified target(s)
+ */
+ VARIABLE: 'variable'
};
module.exports = ArgumentType;
From d2c123fd12db2ba81a50e41f75a29ffc4e4006a7 Mon Sep 17 00:00:00 2001
From: LilyMakesThings <127533508+LilyMakesThings@users.noreply.github.com>
Date: Mon, 12 Feb 2024 17:39:07 +0000
Subject: [PATCH 009/202] Allow custom variable declaration
---
src/engine/runtime.js | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/src/engine/runtime.js b/src/engine/runtime.js
index a5ee3562c66..5f5dffc26bd 100644
--- a/src/engine/runtime.js
+++ b/src/engine/runtime.js
@@ -1571,7 +1571,7 @@ class Runtime extends EventEmitter {
}
/**
- * Helper for _convertPlaceholdes which handles inline images which are a specialized case of block "arguments".
+ * Helper for _convertPlaceholders which handles inline images which are a specialized case of block "arguments".
* @param {object} argInfo Metadata about the inline image as specified by the extension
* @return {object} JSON blob for a scratch-blocks image field.
* @private
@@ -1594,19 +1594,20 @@ class Runtime extends EventEmitter {
}
/**
- * Helper for _convertPlaceholdes which handles variable fields which are a specialized case of block "arguments".
+ * Helper for _convertPlaceholders which handles variable fields which are a specialized case of block "arguments".
* @param {object} argInfo Metadata about the inline image as specified by the extension
* @return {object} JSON blob for a scratch-blocks variable field.
* @private
*/
_constructVariableJson (argInfo, placeholder) {
+ const variable = argInfo.variable;
return {
type: 'field_variable',
name: placeholder,
variableTypes:
// eslint-disable-next-line max-len
argInfo.variableTypes ? (Array.isArray(argInfo.variableTypes) ? argInfo.variableTypes : [argInfo.variableTypes]) : [''],
- variable: (argInfo.variableTypes === 'broadcast_msg') ? 'message1' : null,
+ variable: variable ?? (argInfo.variableTypes === 'broadcast_msg') ? 'message1' : null;
filter: argInfo.filter ?? []
};
}
From fdebbe0254713069925816e7f814a5a212acb94b Mon Sep 17 00:00:00 2001
From: LilyMakesThings <127533508+LilyMakesThings@users.noreply.github.com>
Date: Mon, 12 Feb 2024 21:17:22 +0000
Subject: [PATCH 010/202] Remove semicolon
---
src/engine/runtime.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/engine/runtime.js b/src/engine/runtime.js
index 5f5dffc26bd..028d156d45f 100644
--- a/src/engine/runtime.js
+++ b/src/engine/runtime.js
@@ -1607,7 +1607,7 @@ class Runtime extends EventEmitter {
variableTypes:
// eslint-disable-next-line max-len
argInfo.variableTypes ? (Array.isArray(argInfo.variableTypes) ? argInfo.variableTypes : [argInfo.variableTypes]) : [''],
- variable: variable ?? (argInfo.variableTypes === 'broadcast_msg') ? 'message1' : null;
+ variable: variable ?? (argInfo.variableTypes === 'broadcast_msg') ? 'message1' : null,
filter: argInfo.filter ?? []
};
}
From 44cb44936dda755be6234bb0310d3c1244e2e50a Mon Sep 17 00:00:00 2001
From: LilyMakesThings <127533508+LilyMakesThings@users.noreply.github.com>
Date: Mon, 12 Feb 2024 23:08:27 +0000
Subject: [PATCH 011/202] Remove variable default name
---
src/engine/runtime.js | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/src/engine/runtime.js b/src/engine/runtime.js
index 028d156d45f..d15574f8351 100644
--- a/src/engine/runtime.js
+++ b/src/engine/runtime.js
@@ -1600,14 +1600,13 @@ class Runtime extends EventEmitter {
* @private
*/
_constructVariableJson (argInfo, placeholder) {
- const variable = argInfo.variable;
return {
type: 'field_variable',
name: placeholder,
variableTypes:
// eslint-disable-next-line max-len
argInfo.variableTypes ? (Array.isArray(argInfo.variableTypes) ? argInfo.variableTypes : [argInfo.variableTypes]) : [''],
- variable: variable ?? (argInfo.variableTypes === 'broadcast_msg') ? 'message1' : null,
+ variable: (argInfo.variableTypes === 'broadcast_msg') ? 'message1' : null,
filter: argInfo.filter ?? []
};
}
From 0e8c4f52d074635425e8581029d1fdacace1c08a Mon Sep 17 00:00:00 2001
From: LilyMakesThings <127533508+LilyMakesThings@users.noreply.github.com>
Date: Tue, 13 Feb 2024 01:56:02 +0000
Subject: [PATCH 012/202] Make some compatibility changes for
ArgumentType.VARIABLE (#6)
* Update runtime.js
* Update execute.js
* Update blocks.js
---
src/engine/blocks.js | 3 +--
src/engine/execute.js | 6 +-----
src/engine/runtime.js | 3 ++-
3 files changed, 4 insertions(+), 8 deletions(-)
diff --git a/src/engine/blocks.js b/src/engine/blocks.js
index 71ace3a205f..ccc0fa06ed8 100644
--- a/src/engine/blocks.js
+++ b/src/engine/blocks.js
@@ -685,8 +685,7 @@ class Blocks {
// Update block value
if (!block.fields[args.name]) return;
- if (args.name === 'VARIABLE' || args.name === 'LIST' ||
- args.name === 'BROADCAST_OPTION') {
+ if (typeof block.fields[args.name].variableTypes !== 'undefined') {
// Get variable name using the id in args.value.
const variable = this.runtime.getEditingTarget().lookupVariableById(args.value);
if (variable) {
diff --git a/src/engine/execute.js b/src/engine/execute.js
index 7e94ee9a36d..663a1d34af2 100644
--- a/src/engine/execute.js
+++ b/src/engine/execute.js
@@ -311,11 +311,7 @@ class BlockCached {
// Store the static fields onto _argValues.
for (const fieldName in fields) {
- if (
- fieldName === 'VARIABLE' ||
- fieldName === 'LIST' ||
- fieldName === 'BROADCAST_OPTION'
- ) {
+ if (typepof fields[fieldName].variableType !== 'undefined') {
this._argValues[fieldName] = {
id: fields[fieldName].id,
name: fields[fieldName].value
diff --git a/src/engine/runtime.js b/src/engine/runtime.js
index d15574f8351..7dcd514f1f6 100644
--- a/src/engine/runtime.js
+++ b/src/engine/runtime.js
@@ -1606,7 +1606,8 @@ class Runtime extends EventEmitter {
variableTypes:
// eslint-disable-next-line max-len
argInfo.variableTypes ? (Array.isArray(argInfo.variableTypes) ? argInfo.variableTypes : [argInfo.variableTypes]) : [''],
- variable: (argInfo.variableTypes === 'broadcast_msg') ? 'message1' : null,
+ // TO DO: default to an existing variable.
+ variable: argInfo.variable ?? (argInfo.variableTypes === 'broadcast_msg') ? 'message1' : null,
filter: argInfo.filter ?? []
};
}
From 14698f813e89dbd00e642c0df861b77fd3260043 Mon Sep 17 00:00:00 2001
From: LilyMakesThings <127533508+LilyMakesThings@users.noreply.github.com>
Date: Tue, 13 Feb 2024 01:57:06 +0000
Subject: [PATCH 013/202] Typo
---
src/engine/execute.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/engine/execute.js b/src/engine/execute.js
index 663a1d34af2..c5bd08bf601 100644
--- a/src/engine/execute.js
+++ b/src/engine/execute.js
@@ -311,7 +311,7 @@ class BlockCached {
// Store the static fields onto _argValues.
for (const fieldName in fields) {
- if (typepof fields[fieldName].variableType !== 'undefined') {
+ if (typepof fields[fieldName].variableTypes !== 'undefined') {
this._argValues[fieldName] = {
id: fields[fieldName].id,
name: fields[fieldName].value
From 7df93a01ad1a3f9f228358a4b86594e52e9dc7c5 Mon Sep 17 00:00:00 2001
From: LilyMakesThings <127533508+LilyMakesThings@users.noreply.github.com>
Date: Tue, 13 Feb 2024 02:53:10 +0000
Subject: [PATCH 014/202] Typo
---
src/engine/execute.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/engine/execute.js b/src/engine/execute.js
index c5bd08bf601..10c20615ad6 100644
--- a/src/engine/execute.js
+++ b/src/engine/execute.js
@@ -311,7 +311,7 @@ class BlockCached {
// Store the static fields onto _argValues.
for (const fieldName in fields) {
- if (typepof fields[fieldName].variableTypes !== 'undefined') {
+ if (typeof fields[fieldName].variableTypes !== 'undefined') {
this._argValues[fieldName] = {
id: fields[fieldName].id,
name: fields[fieldName].value
From f0830c584b40322c5488823abad5e46dee5a2ed7 Mon Sep 17 00:00:00 2001
From: LilyMakesThings <127533508+LilyMakesThings@users.noreply.github.com>
Date: Tue, 13 Feb 2024 17:10:48 +0000
Subject: [PATCH 015/202] variableTypes > variableType
---
src/engine/execute.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/engine/execute.js b/src/engine/execute.js
index 10c20615ad6..531c1b3ced3 100644
--- a/src/engine/execute.js
+++ b/src/engine/execute.js
@@ -311,7 +311,7 @@ class BlockCached {
// Store the static fields onto _argValues.
for (const fieldName in fields) {
- if (typeof fields[fieldName].variableTypes !== 'undefined') {
+ if (typeof fields[fieldName].variableType !== 'undefined') {
this._argValues[fieldName] = {
id: fields[fieldName].id,
name: fields[fieldName].value
From 0bbdb3e6dbcccf20c4849610242ca1e3088cd792 Mon Sep 17 00:00:00 2001
From: LilyMakesThings <127533508+LilyMakesThings@users.noreply.github.com>
Date: Tue, 13 Feb 2024 17:11:28 +0000
Subject: [PATCH 016/202] variableTypes > variableType
---
src/engine/blocks.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/engine/blocks.js b/src/engine/blocks.js
index ccc0fa06ed8..bb397832cae 100644
--- a/src/engine/blocks.js
+++ b/src/engine/blocks.js
@@ -685,7 +685,7 @@ class Blocks {
// Update block value
if (!block.fields[args.name]) return;
- if (typeof block.fields[args.name].variableTypes !== 'undefined') {
+ if (typeof block.fields[args.name].variableType !== 'undefined') {
// Get variable name using the id in args.value.
const variable = this.runtime.getEditingTarget().lookupVariableById(args.value);
if (variable) {
From a90754dea7877ef5adbd1ea0a368ee3c44361080 Mon Sep 17 00:00:00 2001
From: LilyMakesThings <127533508+LilyMakesThings@users.noreply.github.com>
Date: Tue, 13 Feb 2024 22:27:17 +0000
Subject: [PATCH 017/202] is TurboWarp? > is Unsandboxed? (#9)
---
src/blocks/scratch3_procedures.js | 2 +-
src/compiler/irgen.js | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/blocks/scratch3_procedures.js b/src/blocks/scratch3_procedures.js
index 679fdf128e8..1f55d32bf16 100644
--- a/src/blocks/scratch3_procedures.js
+++ b/src/blocks/scratch3_procedures.js
@@ -122,7 +122,7 @@ class Scratch3ProcedureBlocks {
if (util.target.runtime.compilerOptions.enabled && lowercaseValue === 'is compiled?') {
return true;
}
- if (lowercaseValue === 'is turbowarp?') {
+ if (lowercaseValue === 'is unsandboxed?') {
return true;
}
// When the parameter is not found in the most recent procedure
diff --git a/src/compiler/irgen.js b/src/compiler/irgen.js
index acd32ef2e04..1fe9a8efc21 100644
--- a/src/compiler/irgen.js
+++ b/src/compiler/irgen.js
@@ -210,7 +210,7 @@ class ScriptTreeGenerator {
const name = block.fields.VALUE.value;
const index = this.script.arguments.lastIndexOf(name);
if (index === -1) {
- if (name.toLowerCase() === 'is compiled?' || name.toLowerCase() === 'is turbowarp?') {
+ if (name.toLowerCase() === 'is compiled?' || name.toLowerCase() === 'is unsandboxed?') {
return {
kind: 'constant',
value: true
From 175df9d3b062ba445b2b21b0ad81bf89c3fd331d Mon Sep 17 00:00:00 2001
From: LilyMakesThings <127533508+LilyMakesThings@users.noreply.github.com>
Date: Tue, 13 Feb 2024 23:02:10 +0000
Subject: [PATCH 018/202] Update Unsandboxed blocks
---
src/extensions/tw/index.js | 14 +++++++-------
1 file changed, 7 insertions(+), 7 deletions(-)
diff --git a/src/extensions/tw/index.js b/src/extensions/tw/index.js
index 9485ff006cf..465c1b60347 100644
--- a/src/extensions/tw/index.js
+++ b/src/extensions/tw/index.js
@@ -7,7 +7,7 @@ const Cast = require('../../util/cast');
const iconURI = `data:image/svg+xml;base64,${btoa('')}`;
/**
- * Class for TurboWarp blocks
+ * Class for Unsandboxed blocks
* @constructor
*/
class TurboWarpBlocks {
@@ -25,13 +25,13 @@ class TurboWarpBlocks {
getInfo () {
return {
id: 'tw',
- name: 'TurboWarp',
- color1: '#ff4c4c',
- color2: '#e64444',
- color3: '#c73a3a',
+ name: 'Unsandboxed',
+ color1: '#66757f',
+ color2: '#5c6a73',
+ color3: '#525e66',
docsURI: 'https://docs.turbowarp.org/blocks',
- menuIconURI: iconURI,
- blockIconURI: iconURI,
+ // menuIconURI: iconURI,
+ // blockIconURI: iconURI,
blocks: [
{
opcode: 'getLastKeyPressed',
From 5e3bcc7c09186a66612cebd27abc4f00fcdf7c28 Mon Sep 17 00:00:00 2001
From: LilyMakesThings <127533508+LilyMakesThings@users.noreply.github.com>
Date: Wed, 14 Feb 2024 01:00:56 +0000
Subject: [PATCH 019/202] Allow Interpolation to be enabled on individual
targets (#10)
---
src/engine/runtime.js | 9 +++++++--
src/engine/tw-interpolate.js | 3 ++-
src/sprites/rendered-target.js | 7 +++++++
3 files changed, 16 insertions(+), 3 deletions(-)
diff --git a/src/engine/runtime.js b/src/engine/runtime.js
index 7dcd514f1f6..e06292979d1 100644
--- a/src/engine/runtime.js
+++ b/src/engine/runtime.js
@@ -2520,7 +2520,12 @@ class Runtime extends EventEmitter {
* inactive threads after each iteration.
*/
_step () {
- if (this.interpolationEnabled) {
+ let targetHasInterpolation = false;
+ for (const target of this.targets) {
+ if (target.interpolation) targetHasInterpolation = true;
+ }
+
+ if (this.interpolationEnabled || targetHasInterpolation) {
interpolate.setupInitialState(this);
}
@@ -2600,7 +2605,7 @@ class Runtime extends EventEmitter {
this.profiler.reportFrames();
}
- if (this.interpolationEnabled) {
+ if (this.interpolationEnabled || targetHasInterpolation) {
this._lastStepTime = Date.now();
}
}
diff --git a/src/engine/tw-interpolate.js b/src/engine/tw-interpolate.js
index 3556dffd224..ce7d1580180 100644
--- a/src/engine/tw-interpolate.js
+++ b/src/engine/tw-interpolate.js
@@ -46,7 +46,8 @@ const interpolate = (runtime, time) => {
// interpolationData is the initial state at the start of the frame (time 0)
// the state on the target itself is the state at the end of the frame (time 1)
const interpolationData = target.interpolationData;
- if (!interpolationData) {
+ const interpolationEnabled = target.interpolation;
+ if !(interpolationData || interpolationEnabled) {
continue;
}
diff --git a/src/sprites/rendered-target.js b/src/sprites/rendered-target.js
index eeb029f3874..26e04469f6e 100644
--- a/src/sprites/rendered-target.js
+++ b/src/sprites/rendered-target.js
@@ -167,6 +167,7 @@ class RenderedTarget extends Target {
this.onTargetMoved = null;
this.onTargetVisualChange = null;
+ this.interpolation = false;
this.interpolationData = null;
}
@@ -1066,6 +1067,12 @@ class RenderedTarget extends Target {
this.dragging = false;
}
+ /**
+ * Set whether the sprite is interpolated.
+ */
+ setInterpolation(interpolated) {
+ this.interpolation = interpolated;
+ }
/**
* Serialize sprite info, used when emitting events about the sprite
From 682b5e83d04d9214be197c371bce3b2a5d3bf14a Mon Sep 17 00:00:00 2001
From: LilyMakesThings <127533508+LilyMakesThings@users.noreply.github.com>
Date: Wed, 14 Feb 2024 01:24:46 +0000
Subject: [PATCH 020/202] illegal
---
src/engine/tw-interpolate.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/engine/tw-interpolate.js b/src/engine/tw-interpolate.js
index ce7d1580180..71bfa381e90 100644
--- a/src/engine/tw-interpolate.js
+++ b/src/engine/tw-interpolate.js
@@ -47,7 +47,7 @@ const interpolate = (runtime, time) => {
// the state on the target itself is the state at the end of the frame (time 1)
const interpolationData = target.interpolationData;
const interpolationEnabled = target.interpolation;
- if !(interpolationData || interpolationEnabled) {
+ if (!(interpolationData || interpolationEnabled)) {
continue;
}
From ee2bbb480f4c665e351eab385a091e7d7d2c0c09 Mon Sep 17 00:00:00 2001
From: LilyMakesThings <127533508+LilyMakesThings@users.noreply.github.com>
Date: Wed, 14 Feb 2024 02:44:40 +0000
Subject: [PATCH 021/202] Add acceptReporters to (some) argument types (#11)
---
src/engine/runtime.js | 37 +++++++++++++++++++++++++++++++++----
1 file changed, 33 insertions(+), 4 deletions(-)
diff --git a/src/engine/runtime.js b/src/engine/runtime.js
index e06292979d1..c71c863e445 100644
--- a/src/engine/runtime.js
+++ b/src/engine/runtime.js
@@ -130,6 +130,23 @@ const ArgumentTypeMap = (() => {
return map;
})();
+const FieldTypeMap = (() => {
+ const map = {};
+ map[ArgumentType.ANGLE] = {
+ fieldName: "field_angle",
+ };
+ map[ArgumentType.NUMBER] = {
+ fieldName: "field_number",
+ };
+ map[ArgumentType.STRING] = {
+ fieldName: "field_input",
+ };
+ map[ArgumentType.NOTE] = {
+ fieldName: "field_note",
+ };
+ return map;
+})();
+
/**
* A pair of functions used to manage the cloud variable limit,
* to be used when adding (or attempting to add) or removing a cloud variable.
@@ -1226,8 +1243,7 @@ class Runtime extends EventEmitter {
colour: menuInfo.acceptText ? '#FFFFFF' : categoryInfo.color1,
colourSecondary: menuInfo.acceptText ? '#FFFFFF' : categoryInfo.color2,
colourTertiary: menuInfo.acceptText ? '#FFFFFF' : categoryInfo.color3,
- outputShape: menuInfo.acceptReporters ?
- ScratchBlocksConstants.OUTPUT_SHAPE_ROUND : ScratchBlocksConstants.OUTPUT_SHAPE_SQUARE,
+ outputShape: ScratchBlocksConstants.OUTPUT_SHAPE_ROUND,
args0: [
{// to do: we could reimplement field_numberdropdown here really easily
type: menuInfo.acceptText ?
@@ -1639,7 +1655,7 @@ class Runtime extends EventEmitter {
// check if this is not one of those cases. E.g. an inline image on a block.
if (argTypeInfo.fieldType === 'field_image') {
argJSON = this._constructInlineImageJson(argInfo);
- } else if (argTypeInfo.fieldType === 'field_variable') {
+ } else if (argTypeInfo.fieldType === 'field_variable' &&) {
argJSON = this._constructVariableJson(argInfo, placeholder);
} else {
// Construct input value
@@ -1666,7 +1682,15 @@ class Runtime extends EventEmitter {
let fieldName;
if (argInfo.menu) {
const menuInfo = context.categoryInfo.menuInfo[argInfo.menu];
- if (menuInfo.acceptReporters) {
+
+ let acceptReporters = false;
+ if (typeof argInfo.acceptReporters !== "undefined") {
+ acceptReporters = argInfo.acceptReporters;
+ } else if (typeof menuInfo.acceptReporters !== "undefined") {
+ acceptReporters = menuInfo.acceptReporters;
+ }
+
+ if (acceptReporters) {
valueName = placeholder;
shadowType = this._makeExtensionMenuId(argInfo.menu, context.categoryInfo.id);
fieldName = argInfo.menu;
@@ -1677,6 +1701,11 @@ class Runtime extends EventEmitter {
shadowType = null;
fieldName = placeholder;
}
+ } else if (argInfo.acceptReporters === false && FieldTypeMap[argInfo.type]) {
+ argJSON.type = FieldTypeMap[argInfo.type].fieldName;
+ valueName = null;
+ shadowType = null;
+ fieldName = placeholder;
} else {
valueName = placeholder;
shadowType = (argTypeInfo.shadow && argTypeInfo.shadow.type) || null;
From db687f37be2eaadc442cc9beb422c056b7938a75 Mon Sep 17 00:00:00 2001
From: LilyMakesThings <127533508+LilyMakesThings@users.noreply.github.com>
Date: Wed, 14 Feb 2024 02:59:48 +0000
Subject: [PATCH 022/202] add number dropdowns
---
src/engine/runtime.js | 15 +++++++++------
1 file changed, 9 insertions(+), 6 deletions(-)
diff --git a/src/engine/runtime.js b/src/engine/runtime.js
index c71c863e445..ba6d2a4e449 100644
--- a/src/engine/runtime.js
+++ b/src/engine/runtime.js
@@ -1234,20 +1234,23 @@ class Runtime extends EventEmitter {
_buildMenuForScratchBlocks (menuName, menuInfo, categoryInfo) {
const menuId = this._makeExtensionMenuId(menuName, categoryInfo.id);
const menuItems = this._convertMenuItems(menuInfo.items);
+ const acceptInput = (menuInfo.acceptText || menuInfo.acceptNumber);
+ const type = menuInfo.acceptText ?
+ 'field_textdropdown' : menuInfo.acceptNumber ?
+ 'field_numberdropdown' : 'field_dropdown';
return {
json: {
message0: '%1',
type: menuId,
inputsInline: true,
output: 'String',
- colour: menuInfo.acceptText ? '#FFFFFF' : categoryInfo.color1,
- colourSecondary: menuInfo.acceptText ? '#FFFFFF' : categoryInfo.color2,
- colourTertiary: menuInfo.acceptText ? '#FFFFFF' : categoryInfo.color3,
+ colour: acceptInput ? '#FFFFFF' : categoryInfo.color1,
+ colourSecondary: acceptInput ? '#FFFFFF' : categoryInfo.color2,
+ colourTertiary: acceptInput ? '#FFFFFF' : categoryInfo.color3,
outputShape: ScratchBlocksConstants.OUTPUT_SHAPE_ROUND,
args0: [
- {// to do: we could reimplement field_numberdropdown here really easily
- type: menuInfo.acceptText ?
- 'field_textdropdown' : 'field_dropdown',
+ {
+ type,
name: menuName,
options: menuItems
}
From 4151c5bd2e9b839e4c3fc8adfa078e0b804ef3ae Mon Sep 17 00:00:00 2001
From: LilyMakesThings <127533508+LilyMakesThings@users.noreply.github.com>
Date: Wed, 14 Feb 2024 03:14:56 +0000
Subject: [PATCH 023/202] fix typo
---
src/engine/runtime.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/engine/runtime.js b/src/engine/runtime.js
index ba6d2a4e449..acbc383152e 100644
--- a/src/engine/runtime.js
+++ b/src/engine/runtime.js
@@ -1658,7 +1658,7 @@ class Runtime extends EventEmitter {
// check if this is not one of those cases. E.g. an inline image on a block.
if (argTypeInfo.fieldType === 'field_image') {
argJSON = this._constructInlineImageJson(argInfo);
- } else if (argTypeInfo.fieldType === 'field_variable' &&) {
+ } else if (argTypeInfo.fieldType === 'field_variable') {
argJSON = this._constructVariableJson(argInfo, placeholder);
} else {
// Construct input value
From 776515fe2b324d3f1601da49960b6f8247aa0e1f Mon Sep 17 00:00:00 2001
From: LilyMakesThings <127533508+LilyMakesThings@users.noreply.github.com>
Date: Wed, 14 Feb 2024 03:27:03 +0000
Subject: [PATCH 024/202] Fix conditions for this
---
src/engine/tw-interpolate.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/engine/tw-interpolate.js b/src/engine/tw-interpolate.js
index 71bfa381e90..785af8238c2 100644
--- a/src/engine/tw-interpolate.js
+++ b/src/engine/tw-interpolate.js
@@ -47,7 +47,7 @@ const interpolate = (runtime, time) => {
// the state on the target itself is the state at the end of the frame (time 1)
const interpolationData = target.interpolationData;
const interpolationEnabled = target.interpolation;
- if (!(interpolationData || interpolationEnabled)) {
+ if (!interpolationData || !interpolationEnabled)) {
continue;
}
From 8035d0886c912b150e55cd863b86b08cb61a4e3c Mon Sep 17 00:00:00 2001
From: LilyMakesThings <127533508+LilyMakesThings@users.noreply.github.com>
Date: Wed, 14 Feb 2024 03:31:43 +0000
Subject: [PATCH 025/202] typo
---
src/engine/tw-interpolate.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/engine/tw-interpolate.js b/src/engine/tw-interpolate.js
index 785af8238c2..4396c79df4b 100644
--- a/src/engine/tw-interpolate.js
+++ b/src/engine/tw-interpolate.js
@@ -47,7 +47,7 @@ const interpolate = (runtime, time) => {
// the state on the target itself is the state at the end of the frame (time 1)
const interpolationData = target.interpolationData;
const interpolationEnabled = target.interpolation;
- if (!interpolationData || !interpolationEnabled)) {
+ if (!interpolationData || !interpolationEnabled) {
continue;
}
From 8c0bc4098e240dc6419913c4bfbe68fdc55e93e8 Mon Sep 17 00:00:00 2001
From: LilyMakesThings <127533508+LilyMakesThings@users.noreply.github.com>
Date: Thu, 15 Feb 2024 01:46:22 +0000
Subject: [PATCH 026/202] Remove limits and fencing by default
---
src/engine/runtime.js | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/engine/runtime.js b/src/engine/runtime.js
index acbc383152e..b933efd0bc0 100644
--- a/src/engine/runtime.js
+++ b/src/engine/runtime.js
@@ -473,8 +473,8 @@ class Runtime extends EventEmitter {
this.runtimeOptions = {
maxClones: Runtime.MAX_CLONES,
- miscLimits: true,
- fencing: true
+ miscLimits: false,
+ fencing: false
};
this.compilerOptions = {
From 5ae81cdccd3fb3edb48d2fb45f693c3bc08ac455 Mon Sep 17 00:00:00 2001
From: LilyMakesThings <127533508+LilyMakesThings@users.noreply.github.com>
Date: Thu, 15 Feb 2024 05:19:30 +0000
Subject: [PATCH 027/202] Add new blocks + Overhaul palette (vm part) (#7)
---
src/blocks/scratch3_event.js | 6 ++++++
src/blocks/scratch3_looks.js | 7 +++++++
src/blocks/scratch3_motion.js | 24 ++++++++++++++++++++++++
src/blocks/scratch3_operators.js | 13 +++++++++++++
src/compiler/compat-blocks.js | 2 ++
src/compiler/irgen.js | 13 +++++++++++++
src/compiler/jsgen.js | 8 ++++++++
7 files changed, 73 insertions(+)
diff --git a/src/blocks/scratch3_event.js b/src/blocks/scratch3_event.js
index 10ceb10cc3d..4992ad1a5b5 100644
--- a/src/blocks/scratch3_event.js
+++ b/src/blocks/scratch3_event.js
@@ -24,6 +24,7 @@ class Scratch3EventBlocks {
*/
getPrimitives () {
return {
+ event_when: this.when,
event_whentouchingobject: this.touchingObject,
event_broadcast: this.broadcast,
event_broadcastandwait: this.broadcastAndWait,
@@ -62,6 +63,11 @@ class Scratch3EventBlocks {
};
}
+ when (args, util) {
+ const condition = Cast.toBoolean(args.CONDITION);
+ return condition;
+ }
+
touchingObject (args, util) {
return util.target.isTouchingObject(args.TOUCHINGOBJECTMENU);
}
diff --git a/src/blocks/scratch3_looks.js b/src/blocks/scratch3_looks.js
index e84e0f83893..ba13dd7ba2e 100644
--- a/src/blocks/scratch3_looks.js
+++ b/src/blocks/scratch3_looks.js
@@ -298,6 +298,7 @@ class Scratch3LooksBlocks {
looks_changeeffectby: this.changeEffect,
looks_seteffectto: this.setEffect,
looks_cleargraphiceffects: this.clearEffects,
+ looks_effect: this.getEffect,
looks_changesizeby: this.changeSize,
looks_setsizeto: this.setSize,
looks_changestretchby: () => {}, // legacy no-op blocks
@@ -560,6 +561,12 @@ class Scratch3LooksBlocks {
util.target.clearEffects();
}
+ getEffect (args, util) {
+ const effect = Cast.toString(args.EFFECT).toLowerCase();
+ const value = util.target.effects[effect];
+ return Cast.toNumber(value);
+ }
+
changeSize (args, util) {
const change = Cast.toNumber(args.CHANGE);
util.target.setSize(util.target.size + change);
diff --git a/src/blocks/scratch3_motion.js b/src/blocks/scratch3_motion.js
index 057d73db124..72da61b3cb6 100644
--- a/src/blocks/scratch3_motion.js
+++ b/src/blocks/scratch3_motion.js
@@ -20,10 +20,12 @@ class Scratch3MotionBlocks {
motion_movesteps: this.moveSteps,
motion_gotoxy: this.goToXY,
motion_goto: this.goTo,
+ motion_changebyxy: this.changeByXY,
motion_turnright: this.turnRight,
motion_turnleft: this.turnLeft,
motion_pointindirection: this.pointInDirection,
motion_pointtowards: this.pointTowards,
+ motion_pointtowards: this.pointTowardsXY,
motion_glidesecstoxy: this.glide,
motion_glideto: this.glideTo,
motion_ifonedgebounce: this.ifOnEdgeBounce,
@@ -35,6 +37,7 @@ class Scratch3MotionBlocks {
motion_xposition: this.getX,
motion_yposition: this.getY,
motion_direction: this.getDirection,
+ motion_rotationstyle: this.getRotationStyle,
// Legacy no-op blocks:
motion_scroll_right: () => {},
motion_scroll_up: () => {},
@@ -57,6 +60,10 @@ class Scratch3MotionBlocks {
motion_direction: {
isSpriteSpecific: true,
getId: targetId => `${targetId}_direction`
+ },
+ motion_rotationstyle: {
+ isSpriteSpecific: true,
+ getId: targetId => `${targetId}_rotationstyle`
}
};
}
@@ -78,6 +85,12 @@ class Scratch3MotionBlocks {
util.target.setXY(x, y);
}
+ changeByXY (args, util) {
+ const dx = Cast.toNumber(args.X);
+ const dy = Cast.toNumber(args.Y);
+ util.target.setXY(util.target.x + dx, util.target.y + dy);
+ }
+
getTargetXY (targetName, util) {
let targetX = 0;
let targetY = 0;
@@ -144,6 +157,13 @@ class Scratch3MotionBlocks {
util.target.setDirection(direction);
}
+ pointTowardsXY (args, util) {
+ const dx = Cast.toNumber(args.X) - util.target.x;
+ const dy = Cast.toNumber(args.Y) - util.target.y;
+ const direction = 90 - MathUtil.radToDeg(Math.atan2(dy, dx));
+ util.target.setDirection(direction);
+ }
+
glide (args, util) {
if (util.stackFrame.timer) {
const timeElapsed = util.stackFrame.timer.timeElapsed();
@@ -281,6 +301,10 @@ class Scratch3MotionBlocks {
return util.target.direction;
}
+ getRotationStyle (args, util) {
+ return util.target.rotationStyle;
+ }
+
// This corresponds to snapToInteger in Scratch 2
limitPrecision (coordinate) {
const rounded = Math.round(coordinate);
diff --git a/src/blocks/scratch3_operators.js b/src/blocks/scratch3_operators.js
index a2a5ab4bd2b..068a7e23312 100644
--- a/src/blocks/scratch3_operators.js
+++ b/src/blocks/scratch3_operators.js
@@ -25,10 +25,12 @@ class Scratch3OperatorsBlocks {
operator_gt: this.gt,
operator_and: this.and,
operator_or: this.or,
+ operator_xor: this.xor,
operator_not: this.not,
operator_random: this.random,
operator_join: this.join,
operator_letter_of: this.letterOf,
+ operator_letters_of: this.lettersOf,
operator_length: this.length,
operator_contains: this.contains,
operator_mod: this.mod,
@@ -73,6 +75,10 @@ class Scratch3OperatorsBlocks {
return Cast.toBoolean(args.OPERAND1) || Cast.toBoolean(args.OPERAND2);
}
+ xor (args) {
+ return Cast.toBoolean(args.OPERAND1) !== Cast.toBoolean(args.OPERAND2);
+ }
+
not (args) {
return !Cast.toBoolean(args.OPERAND);
}
@@ -107,6 +113,13 @@ class Scratch3OperatorsBlocks {
return str.charAt(index);
}
+ lettersOf (args) {
+ const index1 = Cast.toNumber(args.LETTER1) - 1;
+ const index2 = Cast.toNumber(args.LETTER2) - 1;
+ const str = Cast.toString(args.STRING);
+ return str.slice(Math.max(index1, 1), Math.min(str.length, index2));
+ }
+
length (args) {
return Cast.toString(args.STRING).length;
}
diff --git a/src/compiler/compat-blocks.js b/src/compiler/compat-blocks.js
index 1c9d8f5ae9a..51094bedbaf 100644
--- a/src/compiler/compat-blocks.js
+++ b/src/compiler/compat-blocks.js
@@ -18,7 +18,9 @@ const stacked = [
'motion_glidesecstoxy',
'motion_glideto',
'motion_goto',
+ 'motion_changebyxy',
'motion_pointtowards',
+ 'motion_pointtowardsxy',
'motion_scroll_right',
'motion_scroll_up',
'sensing_askandwait',
diff --git a/src/compiler/irgen.js b/src/compiler/irgen.js
index 1fe9a8efc21..f62c8c2ba41 100644
--- a/src/compiler/irgen.js
+++ b/src/compiler/irgen.js
@@ -366,6 +366,13 @@ class ScriptTreeGenerator {
letter: this.descendInputOfBlock(block, 'LETTER'),
string: this.descendInputOfBlock(block, 'STRING')
};
+ case 'operator_letters_of':
+ return {
+ kind: 'op.lettersOf',
+ left: this.descendInputOfBlock(block, 'LETTER1'),
+ right: this.descendInputOfBlock(block, 'LETTER2'),
+ string: this.descendInputOfBlock(block, 'STRING')
+ };
case 'operator_lt':
return {
kind: 'op.less',
@@ -461,6 +468,12 @@ class ScriptTreeGenerator {
left: this.descendInputOfBlock(block, 'OPERAND1'),
right: this.descendInputOfBlock(block, 'OPERAND2')
};
+ case 'operator_xor':
+ return {
+ kind: 'op.xor',
+ left: this.descendInputOfBlock(block, 'OPERAND1'),
+ right: this.descendInputOfBlock(block, 'OPERAND2')
+ };
case 'operator_random': {
const from = this.descendInputOfBlock(block, 'FROM');
const to = this.descendInputOfBlock(block, 'TO');
diff --git a/src/compiler/jsgen.js b/src/compiler/jsgen.js
index b9a6c5e538b..e11df633355 100644
--- a/src/compiler/jsgen.js
+++ b/src/compiler/jsgen.js
@@ -472,6 +472,8 @@ class JSGenerator {
case 'list.length':
return new TypedInput(`${this.referenceVariable(node.list)}.value.length`, TYPE_NUMBER);
+ case 'looks.effect':
+ return new TypedInput('Math.round(target)', TYPE_NUMBER);
case 'looks.size':
return new TypedInput('Math.round(target.size)', TYPE_NUMBER);
case 'looks.backdropName':
@@ -485,6 +487,8 @@ class JSGenerator {
case 'motion.direction':
return new TypedInput('target.direction', TYPE_NUMBER);
+ case 'motion.rotationStyle':
+ return new TypedInput('target.rotationStyle', TYPE_NUMBER);
case 'motion.x':
return new TypedInput('limitPrecision(target.x)', TYPE_NUMBER);
case 'motion.y':
@@ -593,6 +597,8 @@ class JSGenerator {
}
case 'op.letterOf':
return new TypedInput(`((${this.descendInput(node.string).asString()})[(${this.descendInput(node.letter).asNumber()} | 0) - 1] || "")`, TYPE_STRING);
+ case 'op.lettersOf':
+ return new TypedInput(`((${this.descendInput(node.string).asString()}).substring(${this.descendInput(node.letter1).asNumber()}, ${this.descendInput(node.letter2).asNumber()}) || "")`, TYPE_STRING);
case 'op.ln':
// Needs to be marked as NaN because Math.log(-1) == NaN
return new TypedInput(`Math.log(${this.descendInput(node.value).asNumber()})`, TYPE_NUMBER_NAN);
@@ -610,6 +616,8 @@ class JSGenerator {
return new TypedInput(`!${this.descendInput(node.operand).asBoolean()}`, TYPE_BOOLEAN);
case 'op.or':
return new TypedInput(`(${this.descendInput(node.left).asBoolean()} || ${this.descendInput(node.right).asBoolean()})`, TYPE_BOOLEAN);
+ case 'op.xor':
+ return new TypedInput(`(${this.descendInput(node.left).asBoolean()} !== ${this.descendInput(node.right).asBoolean()})`, TYPE_BOOLEAN);
case 'op.random':
if (node.useInts) {
// Both inputs are ints, so we know neither are NaN
From 0f45f9cc7612644ed20a5dd72b3495123bfa6fb2 Mon Sep 17 00:00:00 2001
From: LilyMakesThings <127533508+LilyMakesThings@users.noreply.github.com>
Date: Thu, 15 Feb 2024 06:38:13 +0000
Subject: [PATCH 028/202] Fix new blocks (Part 1/?) (#12)
---
src/blocks/scratch3_event.js | 4 ++++
src/blocks/scratch3_motion.js | 6 +++---
src/blocks/scratch3_operators.js | 6 +++---
src/compiler/jsgen.js | 2 +-
4 files changed, 11 insertions(+), 7 deletions(-)
diff --git a/src/blocks/scratch3_event.js b/src/blocks/scratch3_event.js
index 4992ad1a5b5..cf598829f43 100644
--- a/src/blocks/scratch3_event.js
+++ b/src/blocks/scratch3_event.js
@@ -37,6 +37,10 @@ class Scratch3EventBlocks {
event_whenflagclicked: {
restartExistingThreads: true
},
+ event_when: {
+ restartExistingThreads: false,
+ edgeActivated: true
+ },
event_whenkeypressed: {
restartExistingThreads: false
},
diff --git a/src/blocks/scratch3_motion.js b/src/blocks/scratch3_motion.js
index 72da61b3cb6..9a962dd0e70 100644
--- a/src/blocks/scratch3_motion.js
+++ b/src/blocks/scratch3_motion.js
@@ -25,7 +25,7 @@ class Scratch3MotionBlocks {
motion_turnleft: this.turnLeft,
motion_pointindirection: this.pointInDirection,
motion_pointtowards: this.pointTowards,
- motion_pointtowards: this.pointTowardsXY,
+ motion_pointtowardsxy: this.pointTowardsXY,
motion_glidesecstoxy: this.glide,
motion_glideto: this.glideTo,
motion_ifonedgebounce: this.ifOnEdgeBounce,
@@ -85,7 +85,7 @@ class Scratch3MotionBlocks {
util.target.setXY(x, y);
}
- changeByXY (args, util) {
+ changeByXY (args, util) { // used by compiler
const dx = Cast.toNumber(args.X);
const dy = Cast.toNumber(args.Y);
util.target.setXY(util.target.x + dx, util.target.y + dy);
@@ -157,7 +157,7 @@ class Scratch3MotionBlocks {
util.target.setDirection(direction);
}
- pointTowardsXY (args, util) {
+ pointTowardsXY (args, util) { // used by compiler
const dx = Cast.toNumber(args.X) - util.target.x;
const dy = Cast.toNumber(args.Y) - util.target.y;
const direction = 90 - MathUtil.radToDeg(Math.atan2(dy, dx));
diff --git a/src/blocks/scratch3_operators.js b/src/blocks/scratch3_operators.js
index 068a7e23312..9c67754cfc7 100644
--- a/src/blocks/scratch3_operators.js
+++ b/src/blocks/scratch3_operators.js
@@ -114,10 +114,10 @@ class Scratch3OperatorsBlocks {
}
lettersOf (args) {
- const index1 = Cast.toNumber(args.LETTER1) - 1;
- const index2 = Cast.toNumber(args.LETTER2) - 1;
+ const index1 = Cast.toNumber(args.LETTER1);
+ const index2 = Cast.toNumber(args.LETTER2);
const str = Cast.toString(args.STRING);
- return str.slice(Math.max(index1, 1), Math.min(str.length, index2));
+ return str.slice(Math.max(index1, 1) - 1, Math.min(str.length, index2));
}
length (args) {
diff --git a/src/compiler/jsgen.js b/src/compiler/jsgen.js
index e11df633355..cbe199caa18 100644
--- a/src/compiler/jsgen.js
+++ b/src/compiler/jsgen.js
@@ -598,7 +598,7 @@ class JSGenerator {
case 'op.letterOf':
return new TypedInput(`((${this.descendInput(node.string).asString()})[(${this.descendInput(node.letter).asNumber()} | 0) - 1] || "")`, TYPE_STRING);
case 'op.lettersOf':
- return new TypedInput(`((${this.descendInput(node.string).asString()}).substring(${this.descendInput(node.letter1).asNumber()}, ${this.descendInput(node.letter2).asNumber()}) || "")`, TYPE_STRING);
+ return new TypedInput(`((${this.descendInput(node.string).asString()}).substring(${this.descendInput(node.left).asNumber() - 1}, ${this.descendInput(node.right).asNumber() - 1}) || "")`, TYPE_STRING);
case 'op.ln':
// Needs to be marked as NaN because Math.log(-1) == NaN
return new TypedInput(`Math.log(${this.descendInput(node.value).asNumber()})`, TYPE_NUMBER_NAN);
From efefa58f23e7bcbfb396bf0c53fde0026b032c1e Mon Sep 17 00:00:00 2001
From: LilyMakesThings <127533508+LilyMakesThings@users.noreply.github.com>
Date: Thu, 15 Feb 2024 07:28:50 +0000
Subject: [PATCH 029/202] Fix new blocks (Part 2/2) (#13)
---
src/compiler/irgen.js | 9 +++++++++
src/compiler/jsgen.js | 4 ++--
2 files changed, 11 insertions(+), 2 deletions(-)
diff --git a/src/compiler/irgen.js b/src/compiler/irgen.js
index f62c8c2ba41..f1e2c380bfd 100644
--- a/src/compiler/irgen.js
+++ b/src/compiler/irgen.js
@@ -299,7 +299,16 @@ class ScriptTreeGenerator {
return {
kind: 'looks.size'
};
+ case 'looks_effect':
+ return {
+ kind: 'looks.effect',
+ effect: this.descendInputOfBlock(block, 'EFFECT')
+ };
+ case 'motion_rotationstyle':
+ return {
+ kind: 'motion.rotationStyle'
+ };
case 'motion_direction':
return {
kind: 'motion.direction'
diff --git a/src/compiler/jsgen.js b/src/compiler/jsgen.js
index cbe199caa18..ad18e610336 100644
--- a/src/compiler/jsgen.js
+++ b/src/compiler/jsgen.js
@@ -473,7 +473,7 @@ class JSGenerator {
return new TypedInput(`${this.referenceVariable(node.list)}.value.length`, TYPE_NUMBER);
case 'looks.effect':
- return new TypedInput('Math.round(target)', TYPE_NUMBER);
+ return new TypedInput(`Math.round(target.effects[${this.referenceVariable(node.effect)}])`, TYPE_NUMBER);
case 'looks.size':
return new TypedInput('Math.round(target.size)', TYPE_NUMBER);
case 'looks.backdropName':
@@ -598,7 +598,7 @@ class JSGenerator {
case 'op.letterOf':
return new TypedInput(`((${this.descendInput(node.string).asString()})[(${this.descendInput(node.letter).asNumber()} | 0) - 1] || "")`, TYPE_STRING);
case 'op.lettersOf':
- return new TypedInput(`((${this.descendInput(node.string).asString()}).substring(${this.descendInput(node.left).asNumber() - 1}, ${this.descendInput(node.right).asNumber() - 1}) || "")`, TYPE_STRING);
+ return new TypedInput(`((${this.descendInput(node.string).asString()}).substring(${this.descendInput(node.left).asNumber() - 1}, ${this.descendInput(node.right).asNumber()}) || "")`, TYPE_STRING);
case 'op.ln':
// Needs to be marked as NaN because Math.log(-1) == NaN
return new TypedInput(`Math.log(${this.descendInput(node.value).asNumber()})`, TYPE_NUMBER_NAN);
From 6bcd397d7f9c0e40d9bfada27bbfbdcb4fa3a6f9 Mon Sep 17 00:00:00 2001
From: LilyMakesThings <127533508+LilyMakesThings@users.noreply.github.com>
Date: Thu, 15 Feb 2024 18:17:10 +0000
Subject: [PATCH 030/202] Add in-built camera (part 1)
---
src/engine/runtime.js | 52 ++++++++++++++++++++++++++++++++++++++++++
src/io/mouse.js | 4 ++--
src/virtual-machine.js | 10 ++++++++
3 files changed, 64 insertions(+), 2 deletions(-)
diff --git a/src/engine/runtime.js b/src/engine/runtime.js
index b933efd0bc0..3998b84b5f7 100644
--- a/src/engine/runtime.js
+++ b/src/engine/runtime.js
@@ -471,6 +471,9 @@ class Runtime extends EventEmitter {
this.stageWidth = Runtime.STAGE_WIDTH;
this.stageHeight = Runtime.STAGE_HEIGHT;
+ this.cameraX = Runtime.CAMERA_X;
+ this.cameraY = Runtime.CAMERA_Y;
+
this.runtimeOptions = {
maxClones: Runtime.MAX_CLONES,
miscLimits: false,
@@ -569,6 +572,24 @@ class Runtime extends EventEmitter {
return 360;
}
+ /**
+ * X position of camera.
+ * @const {number}
+ */
+ static get CAMERA_X () {
+ // tw: camera is set per-runtime, this is only the initial value
+ return 0;
+ }
+
+ /**
+ * Y position of camera.
+ * @const {number}
+ */
+ static get CAMERA_Y () {
+ // tw: camera is set per-runtime, this is only the initial value
+ return 0;
+ }
+
/**
* Event name for glowing a script.
* @const {string}
@@ -666,6 +687,14 @@ class Runtime extends EventEmitter {
return 'STAGE_SIZE_CHANGED';
}
+ /**
+ * Event name for camera moving.
+ * @const {string}
+ */
+ static get CAMERA_MOVED () {
+ return 'CAMERA_MOVED';
+ }
+
/**
* Event name for compiler errors.
* @const {string}
@@ -2778,6 +2807,29 @@ class Runtime extends EventEmitter {
this.emit(Runtime.STAGE_SIZE_CHANGED, width, height);
}
+ /**
+ * Change X and Y of the camera. This will also inform the renderer of the new camera position.
+ * @param {number} x New camera x
+ * @param {number} y New camera y
+ */
+ setCameraXY(x, y) {
+ if (this.cameraX !== x || this.cameraY !== y) {
+ const deltaX = x - this.cameraX;
+ const deltaY = y - this.cameraY;
+ // Preserve monitor location relative to the center of the stage
+
+ this.cameraX = x;
+ this.cameraY = y;
+ if (this.renderer) {
+ this.renderer.setCameraPosition(
+ x,
+ y
+ );
+ }
+ }
+ this.emit(Runtime.CAMERA_MOVED, x, y);
+ }
+
// eslint-disable-next-line no-unused-vars
setInEditor (inEditor) {
// no-op
diff --git a/src/io/mouse.js b/src/io/mouse.js
index 169e6af0b67..7ef2551cd9b 100644
--- a/src/io/mouse.js
+++ b/src/io/mouse.js
@@ -69,7 +69,7 @@ class Mouse {
this.runtime.stageWidth * ((data.x / data.canvasWidth) - 0.5),
-(this.runtime.stageWidth / 2),
(this.runtime.stageWidth / 2)
- );
+ ) + this.runtime.cameraX;
}
if (typeof data.y === 'number') {
this._clientY = data.y;
@@ -77,7 +77,7 @@ class Mouse {
-this.runtime.stageHeight * ((data.y / data.canvasHeight) - 0.5),
-(this.runtime.stageHeight / 2),
(this.runtime.stageHeight / 2)
- );
+ ) + this.runtime.cameraY;
}
if (typeof data.isDown !== 'undefined') {
// If no button specified, default to left button for compatibility
diff --git a/src/virtual-machine.js b/src/virtual-machine.js
index e1a1fcdea9e..8905face5c5 100644
--- a/src/virtual-machine.js
+++ b/src/virtual-machine.js
@@ -187,6 +187,9 @@ class VirtualMachine extends EventEmitter {
this.runtime.on(Runtime.STAGE_SIZE_CHANGED, (width, height) => {
this.emit(Runtime.STAGE_SIZE_CHANGED, width, height);
});
+ this.runtime.on(Runtime.CAMERA_MOVED, (x, y) => {
+ this.emit(Runtime.CAMERA_MOVED, x, y);
+ });
this.runtime.on(Runtime.COMPILE_ERROR, (target, error) => {
this.emit(Runtime.COMPILE_ERROR, target, error);
});
@@ -306,6 +309,10 @@ class VirtualMachine extends EventEmitter {
this.runtime.setStageSize(width, height);
}
+ setCameraXY (x, y) {
+ this.runtime.setCameraXY(x, y);
+ }
+
setInEditor (inEditor) {
this.runtime.setInEditor(inEditor);
}
@@ -508,6 +515,9 @@ class VirtualMachine extends EventEmitter {
log.error(`Failed to fetch project with id: ${id}`);
return null;
}
+ // usb: switch to 480 when loading from Scratch
+ // (will change automatically if options tell otherwise)
+ vm.setStageSize(480, 360);
return vm.loadProject(projectAsset.data);
});
}
From fb3ba9c062483a6e94c4e3297a095ba191471209 Mon Sep 17 00:00:00 2001
From: LilyMakesThings <127533508+LilyMakesThings@users.noreply.github.com>
Date: Thu, 15 Feb 2024 18:24:51 +0000
Subject: [PATCH 031/202] Update jsgen.js
---
src/compiler/jsgen.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/compiler/jsgen.js b/src/compiler/jsgen.js
index ad18e610336..0a4b6854a31 100644
--- a/src/compiler/jsgen.js
+++ b/src/compiler/jsgen.js
@@ -473,7 +473,7 @@ class JSGenerator {
return new TypedInput(`${this.referenceVariable(node.list)}.value.length`, TYPE_NUMBER);
case 'looks.effect':
- return new TypedInput(`Math.round(target.effects[${this.referenceVariable(node.effect)}])`, TYPE_NUMBER);
+ return new TypedInput(`Math.round(target.effects[${sanitize(node.effect)}])`, TYPE_NUMBER);
case 'looks.size':
return new TypedInput('Math.round(target.size)', TYPE_NUMBER);
case 'looks.backdropName':
From 3904b7fe7279357ecb40e060a2c87e09371b450b Mon Sep 17 00:00:00 2001
From: LilyMakesThings <127533508+LilyMakesThings@users.noreply.github.com>
Date: Thu, 15 Feb 2024 23:26:16 +0000
Subject: [PATCH 032/202] Update jsgen.js
---
src/compiler/jsgen.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/compiler/jsgen.js b/src/compiler/jsgen.js
index 0a4b6854a31..0583e721478 100644
--- a/src/compiler/jsgen.js
+++ b/src/compiler/jsgen.js
@@ -473,7 +473,7 @@ class JSGenerator {
return new TypedInput(`${this.referenceVariable(node.list)}.value.length`, TYPE_NUMBER);
case 'looks.effect':
- return new TypedInput(`Math.round(target.effects[${sanitize(node.effect)}])`, TYPE_NUMBER);
+ return new TypedInput(`Math.round(target.effects["${sanitize(node.effect)}"] || 0)`, TYPE_NUMBER);
case 'looks.size':
return new TypedInput('Math.round(target.size)', TYPE_NUMBER);
case 'looks.backdropName':
From 9e73f53028c16682d89bcd81a7171ff53ff36f0c Mon Sep 17 00:00:00 2001
From: LilyMakesThings <127533508+LilyMakesThings@users.noreply.github.com>
Date: Fri, 16 Feb 2024 00:55:59 +0000
Subject: [PATCH 033/202] Incorporate all camera properties (#15)
---
src/engine/runtime.js | 68 ++++++++++++++++++++++++++++++++----------
src/virtual-machine.js | 4 +--
2 files changed, 54 insertions(+), 18 deletions(-)
diff --git a/src/engine/runtime.js b/src/engine/runtime.js
index 3998b84b5f7..2f6e60b04eb 100644
--- a/src/engine/runtime.js
+++ b/src/engine/runtime.js
@@ -471,8 +471,12 @@ class Runtime extends EventEmitter {
this.stageWidth = Runtime.STAGE_WIDTH;
this.stageHeight = Runtime.STAGE_HEIGHT;
- this.cameraX = Runtime.CAMERA_X;
- this.cameraY = Runtime.CAMERA_Y;
+ this.camera = {
+ x: Runtime.CAMERA_X,
+ y: Runtime.CAMERA_Y,
+ direction: Runtime.CAMERA_DIRECTION,
+ zoom: Runtime.CAMERA_ZOOM
+ }
this.runtimeOptions = {
maxClones: Runtime.MAX_CLONES,
@@ -581,6 +585,24 @@ class Runtime extends EventEmitter {
return 0;
}
+ /**
+ * Initial rotation of camera.
+ * @const {number}
+ */
+ static get CAMERA_DIRECTION () {
+ // tw: camera is set per-runtime, this is only the initial value
+ return 90;
+ }
+
+ /**
+ * Initial zoom of camera.
+ * @const {number}
+ */
+ static get CAMERA_ZOOM () {
+ // tw: camera is set per-runtime, this is only the initial value
+ return 100;
+ }
+
/**
* Y position of camera.
* @const {number}
@@ -691,8 +713,8 @@ class Runtime extends EventEmitter {
* Event name for camera moving.
* @const {string}
*/
- static get CAMERA_MOVED () {
- return 'CAMERA_MOVED';
+ static get CAMREA_NEEDS_UPDATE () {
+ return 'CAMERA_NEEDS_UPDATE';
}
/**
@@ -2808,26 +2830,40 @@ class Runtime extends EventEmitter {
}
/**
- * Change X and Y of the camera. This will also inform the renderer of the new camera position.
+ * Change all values of the camera. This will also inform the renderer of the new camera position.
* @param {number} x New camera x
* @param {number} y New camera y
+ * @param {number} direction New camera direction
+ * @param {number} zoom New camera zoom
*/
- setCameraXY(x, y) {
- if (this.cameraX !== x || this.cameraY !== y) {
- const deltaX = x - this.cameraX;
- const deltaY = y - this.cameraY;
- // Preserve monitor location relative to the center of the stage
+ setCamera(x, y, direction, zoom) {
+ if (
+ this.camera.x !== x ||
+ this.camera.y !== y ||
+ this.camera.direction !== direction ||
+ this.camera.zoom !== zoom
+ ) {
+ this.camera.x = x ?? this.camera.x;
+ this.camera.y = y ?? this.camera.y;
+ this.camera.direction = direction ?? this.camera.direction;
+ this.camera.zoom = zoom ?? this.camera.zoom;
- this.cameraX = x;
- this.cameraY = y;
if (this.renderer) {
- this.renderer.setCameraPosition(
- x,
- y
+ this.renderer.updateCamera(
+ this.camera.x,
+ this.camera.y,
+ this.camera.direction,
+ this.camera.zoom,
);
}
}
- this.emit(Runtime.CAMERA_MOVED, x, y);
+ this.emit(
+ Runtime.CAMERA_NEEDS_UPDATE,
+ this.camera.x,
+ this.camera.y,
+ this.camera.direction,
+ this.camera.zoom
+ );
}
// eslint-disable-next-line no-unused-vars
diff --git a/src/virtual-machine.js b/src/virtual-machine.js
index 8905face5c5..67678f36279 100644
--- a/src/virtual-machine.js
+++ b/src/virtual-machine.js
@@ -187,8 +187,8 @@ class VirtualMachine extends EventEmitter {
this.runtime.on(Runtime.STAGE_SIZE_CHANGED, (width, height) => {
this.emit(Runtime.STAGE_SIZE_CHANGED, width, height);
});
- this.runtime.on(Runtime.CAMERA_MOVED, (x, y) => {
- this.emit(Runtime.CAMERA_MOVED, x, y);
+ this.runtime.on(Runtime.CAMERA_NEEDS_UPDATE, (x, y, direction, zoom) => {
+ this.emit(Runtime.CAMERA_NEEDS_UPDATE, x, y, direction, zoom);
});
this.runtime.on(Runtime.COMPILE_ERROR, (target, error) => {
this.emit(Runtime.COMPILE_ERROR, target, error);
From c7f03bae03ea2800457748b8a9b2efe29a719e11 Mon Sep 17 00:00:00 2001
From: LilyMakesThings <127533508+LilyMakesThings@users.noreply.github.com>
Date: Fri, 16 Feb 2024 02:52:10 +0000
Subject: [PATCH 034/202] Update runtime.js
---
src/engine/runtime.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/engine/runtime.js b/src/engine/runtime.js
index 2f6e60b04eb..7edd5d1dc9e 100644
--- a/src/engine/runtime.js
+++ b/src/engine/runtime.js
@@ -2849,7 +2849,7 @@ class Runtime extends EventEmitter {
this.camera.zoom = zoom ?? this.camera.zoom;
if (this.renderer) {
- this.renderer.updateCamera(
+ this.renderer._updateCamera (
this.camera.x,
this.camera.y,
this.camera.direction,
From 926504614ca9836b3ff21c0b7206f840002bd864 Mon Sep 17 00:00:00 2001
From: LilyMakesThings <127533508+LilyMakesThings@users.noreply.github.com>
Date: Fri, 16 Feb 2024 03:58:38 +0000
Subject: [PATCH 035/202] Update virtual-machine.js
---
src/virtual-machine.js | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/virtual-machine.js b/src/virtual-machine.js
index 67678f36279..360c671021e 100644
--- a/src/virtual-machine.js
+++ b/src/virtual-machine.js
@@ -309,8 +309,8 @@ class VirtualMachine extends EventEmitter {
this.runtime.setStageSize(width, height);
}
- setCameraXY (x, y) {
- this.runtime.setCameraXY(x, y);
+ setCamera (x, y, direction, zoom) {
+ this.runtime.setCamera(x, y, direction, zoom);
}
setInEditor (inEditor) {
From 61e8f5e4b23d59b5d7ad549870494477fc75d9a3 Mon Sep 17 00:00:00 2001
From: LilyMakesThings <127533508+LilyMakesThings@users.noreply.github.com>
Date: Fri, 16 Feb 2024 05:28:52 +0000
Subject: [PATCH 036/202] Camera support (#16)
* Remove bubble bounds
* Change mouse transforms
---
src/blocks/scratch3_looks.js | 3 ++-
src/io/mouse.js | 34 ++++++++++++++++++++++++++++++++++
2 files changed, 36 insertions(+), 1 deletion(-)
diff --git a/src/blocks/scratch3_looks.js b/src/blocks/scratch3_looks.js
index ba13dd7ba2e..bd72ab1c1dc 100644
--- a/src/blocks/scratch3_looks.js
+++ b/src/blocks/scratch3_looks.js
@@ -171,7 +171,8 @@ class Scratch3LooksBlocks {
bottom: target.y
};
}
- const stageSize = this.runtime.renderer.getNativeSize();
+ // usb: remove bounds to support camera
+ const stageSize = [Infinity, Infinity];
const stageBounds = {
left: -stageSize[0] / 2,
right: stageSize[0] / 2,
diff --git a/src/io/mouse.js b/src/io/mouse.js
index 7ef2551cd9b..f0fa7d9ab8c 100644
--- a/src/io/mouse.js
+++ b/src/io/mouse.js
@@ -70,6 +70,23 @@ class Mouse {
-(this.runtime.stageWidth / 2),
(this.runtime.stageWidth / 2)
) + this.runtime.cameraX;
+ // usb: transform based on camera
+ this._clientX = this.runtime.rendeer.translateX(
+ this._clientX,
+ true,
+ 1,
+ true,
+ this._clientY,
+ -1
+ );
+ this._scratchX = this.runtime.renderer.translateX(
+ this._scratchX,
+ false,
+ 1,
+ true,
+ this._scratchY,
+ 1
+ );
}
if (typeof data.y === 'number') {
this._clientY = data.y;
@@ -78,6 +95,23 @@ class Mouse {
-(this.runtime.stageHeight / 2),
(this.runtime.stageHeight / 2)
) + this.runtime.cameraY;
+ // usb: transform based on camera
+ this._clientY = this.runtime.rendeer.translateY(
+ this.clientY,
+ true,
+ 1,
+ true,
+ this.clientX,
+ -1
+ );
+ this._scratchY = this.runtime.renderer.translateY(
+ this._scratchY,
+ false,
+ 1,
+ true,
+ this._scratchX,
+ 1
+ );
}
if (typeof data.isDown !== 'undefined') {
// If no button specified, default to left button for compatibility
From 9e98bf266e2d14d07e1d9a7662913f609ddf3b61 Mon Sep 17 00:00:00 2001
From: LilyMakesThings <127533508+LilyMakesThings@users.noreply.github.com>
Date: Fri, 16 Feb 2024 05:42:16 +0000
Subject: [PATCH 037/202] Fix typos + old transform
---
src/io/mouse.js | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/src/io/mouse.js b/src/io/mouse.js
index f0fa7d9ab8c..e9de3dad035 100644
--- a/src/io/mouse.js
+++ b/src/io/mouse.js
@@ -69,9 +69,9 @@ class Mouse {
this.runtime.stageWidth * ((data.x / data.canvasWidth) - 0.5),
-(this.runtime.stageWidth / 2),
(this.runtime.stageWidth / 2)
- ) + this.runtime.cameraX;
+ );
// usb: transform based on camera
- this._clientX = this.runtime.rendeer.translateX(
+ this._clientX = this.runtime.renderer.translateX(
this._clientX,
true,
1,
@@ -94,9 +94,9 @@ class Mouse {
-this.runtime.stageHeight * ((data.y / data.canvasHeight) - 0.5),
-(this.runtime.stageHeight / 2),
(this.runtime.stageHeight / 2)
- ) + this.runtime.cameraY;
+ );
// usb: transform based on camera
- this._clientY = this.runtime.rendeer.translateY(
+ this._clientY = this.runtime.renderer.translateY(
this.clientY,
true,
1,
From 81180169f4718a76a1182bfbdbe6626820f47b0c Mon Sep 17 00:00:00 2001
From: LilyMakesThings <127533508+LilyMakesThings@users.noreply.github.com>
Date: Fri, 16 Feb 2024 06:08:30 +0000
Subject: [PATCH 038/202] Fix mouse positions (#17)
---
src/io/mouse.js | 34 ++++++++++++++++++----------------
1 file changed, 18 insertions(+), 16 deletions(-)
diff --git a/src/io/mouse.js b/src/io/mouse.js
index e9de3dad035..3bd2a78f037 100644
--- a/src/io/mouse.js
+++ b/src/io/mouse.js
@@ -64,23 +64,24 @@ class Mouse {
*/
postData (data) {
if (typeof data.x === 'number') {
- this._clientX = data.x;
- this._scratchX = MathUtil.clamp(
- this.runtime.stageWidth * ((data.x / data.canvasWidth) - 0.5),
- -(this.runtime.stageWidth / 2),
- (this.runtime.stageWidth / 2)
- );
// usb: transform based on camera
this._clientX = this.runtime.renderer.translateX(
- this._clientX,
+ data.x,
true,
1,
true,
this._clientY,
-1
);
+ const scratchX = MathUtil.clamp(
+ this.runtime.stageWidth * ((data.x / data.canvasWidth) - 0.5),
+ -(this.runtime.stageWidth / 2),
+ (this.runtime.stageWidth / 2)
+ );
+
+ // usb: transform based on camera
this._scratchX = this.runtime.renderer.translateX(
- this._scratchX,
+ scratchX,
false,
1,
true,
@@ -89,23 +90,24 @@ class Mouse {
);
}
if (typeof data.y === 'number') {
- this._clientY = data.y;
- this._scratchY = MathUtil.clamp(
- -this.runtime.stageHeight * ((data.y / data.canvasHeight) - 0.5),
- -(this.runtime.stageHeight / 2),
- (this.runtime.stageHeight / 2)
- );
// usb: transform based on camera
this._clientY = this.runtime.renderer.translateY(
- this.clientY,
+ data.y,
true,
1,
true,
this.clientX,
-1
);
+ const scratchY = MathUtil.clamp(
+ -this.runtime.stageHeight * ((data.y / data.canvasHeight) - 0.5),
+ -(this.runtime.stageHeight / 2),
+ (this.runtime.stageHeight / 2)
+ );
+
+ // usb: transform based on camera
this._scratchY = this.runtime.renderer.translateY(
- this._scratchY,
+ scratchY,
false,
1,
true,
From e96f1628e848cce6c2ed54138e7444476e6538fa Mon Sep 17 00:00:00 2001
From: LilyMakesThings <127533508+LilyMakesThings@users.noreply.github.com>
Date: Fri, 16 Feb 2024 06:30:03 +0000
Subject: [PATCH 039/202] More mouse fixes
---
src/io/mouse.js | 57 +++++++++++--------------------------------------
1 file changed, 12 insertions(+), 45 deletions(-)
diff --git a/src/io/mouse.js b/src/io/mouse.js
index 3bd2a78f037..56b3bc1a8c7 100644
--- a/src/io/mouse.js
+++ b/src/io/mouse.js
@@ -65,55 +65,20 @@ class Mouse {
postData (data) {
if (typeof data.x === 'number') {
// usb: transform based on camera
- this._clientX = this.runtime.renderer.translateX(
- data.x,
- true,
- 1,
- true,
- this._clientY,
- -1
- );
- const scratchX = MathUtil.clamp(
+ this._clientX = data.x;
+ this._scratchX = MathUtil.clamp(
this.runtime.stageWidth * ((data.x / data.canvasWidth) - 0.5),
-(this.runtime.stageWidth / 2),
(this.runtime.stageWidth / 2)
);
-
- // usb: transform based on camera
- this._scratchX = this.runtime.renderer.translateX(
- scratchX,
- false,
- 1,
- true,
- this._scratchY,
- 1
- );
}
if (typeof data.y === 'number') {
- // usb: transform based on camera
- this._clientY = this.runtime.renderer.translateY(
- data.y,
- true,
- 1,
- true,
- this.clientX,
- -1
- );
- const scratchY = MathUtil.clamp(
+ this._clientY = data.y;
+ this._scratchY = MathUtil.clamp(
-this.runtime.stageHeight * ((data.y / data.canvasHeight) - 0.5),
-(this.runtime.stageHeight / 2),
(this.runtime.stageHeight / 2)
);
-
- // usb: transform based on camera
- this._scratchY = this.runtime.renderer.translateY(
- scratchY,
- false,
- 1,
- true,
- this._scratchX,
- 1
- );
}
if (typeof data.isDown !== 'undefined') {
// If no button specified, default to left button for compatibility
@@ -156,7 +121,7 @@ class Mouse {
* @return {number} Non-clamped X position of the mouse cursor.
*/
getClientX () {
- return this._clientX;
+ return this.runtime.renderer.translateX(this._clientX, true, 1, true, this._clientY, -1);
}
/**
@@ -164,7 +129,7 @@ class Mouse {
* @return {number} Non-clamped Y position of the mouse cursor.
*/
getClientY () {
- return this._clientY;
+ return this.runtime.renderer.translateY(this._clientY, true, -1, true, this._clientX, 1);
}
/**
@@ -172,10 +137,11 @@ class Mouse {
* @return {number} Clamped and integer rounded X position of the mouse cursor.
*/
getScratchX () {
+ const x = this.runtime.renderer.translateX(this._scratchX, false, 1, true, this._scratchY, 1);
if (this.runtime.runtimeOptions.miscLimits) {
- return Math.round(this._scratchX);
+ return Math.round(x);
}
- return roundToThreeDecimals(this._scratchX);
+ return roundToThreeDecimals(x);
}
/**
@@ -183,10 +149,11 @@ class Mouse {
* @return {number} Clamped and integer rounded Y position of the mouse cursor.
*/
getScratchY () {
+ const y = this.runtime.renderer.translateY(this._scratchY, false, 1, true, this._scratchX, 1);
if (this.runtime.runtimeOptions.miscLimits) {
- return Math.round(this._scratchY);
+ return Math.round(y);
}
- return roundToThreeDecimals(this._scratchY);
+ return roundToThreeDecimals(y);
}
/**
From 51e7576a9a8d979c387a6aa7a550a957cac8eab4 Mon Sep 17 00:00:00 2001
From: LilyMakesThings <127533508+LilyMakesThings@users.noreply.github.com>
Date: Fri, 16 Feb 2024 08:30:46 +0000
Subject: [PATCH 040/202] fix mouse again
---
src/io/mouse.js | 36 ++++++++++++++++++++++++++----------
1 file changed, 26 insertions(+), 10 deletions(-)
diff --git a/src/io/mouse.js b/src/io/mouse.js
index 56b3bc1a8c7..53264f52e55 100644
--- a/src/io/mouse.js
+++ b/src/io/mouse.js
@@ -121,7 +121,9 @@ class Mouse {
* @return {number} Non-clamped X position of the mouse cursor.
*/
getClientX () {
- return this.runtime.renderer.translateX(this._clientX, true, 1, true, this._clientY, -1);
+ return this.runtime.renderer.translateX(
+ this._clientX, true, 1, true, this._clientY, -1
+ );
}
/**
@@ -129,7 +131,9 @@ class Mouse {
* @return {number} Non-clamped Y position of the mouse cursor.
*/
getClientY () {
- return this.runtime.renderer.translateY(this._clientY, true, -1, true, this._clientX, 1);
+ return this.runtime.renderer.translateY(
+ this._clientY, true, -1, true, this._clientX, 1
+ );
}
/**
@@ -137,11 +141,11 @@ class Mouse {
* @return {number} Clamped and integer rounded X position of the mouse cursor.
*/
getScratchX () {
- const x = this.runtime.renderer.translateX(this._scratchX, false, 1, true, this._scratchY, 1);
- if (this.runtime.runtimeOptions.miscLimits) {
- return Math.round(x);
- }
- return roundToThreeDecimals(x);
+ const x = this.roundValue(this._scratchX);
+ const y = this.roundValue(this._scratchY);
+ return this.runtime.renderer.translateX(
+ x, false, 1, true, y, 1
+ );
}
/**
@@ -149,11 +153,23 @@ class Mouse {
* @return {number} Clamped and integer rounded Y position of the mouse cursor.
*/
getScratchY () {
- const y = this.runtime.renderer.translateY(this._scratchY, false, 1, true, this._scratchX, 1);
+ const x = this.roundValue(this._scratchX);
+ const y = this.roundValue(this._scratchY);
+ return this.runtime.renderer.translateY(
+ y, false, 1, true, x, 1
+ );
+ }
+
+ /**
+ * Round a mouse coordinate in relation to miscLimits.
+ * @param {number} float The mouse value that needs to be rounded.
+ * @return {number} Clamped and integer rounded Y position of the mouse cursor.
+ */
+ roundValue (float) {
if (this.runtime.runtimeOptions.miscLimits) {
- return Math.round(y);
+ return Math.round(float);
}
- return roundToThreeDecimals(y);
+ return roundToThreeDecimals(float);
}
/**
From 74c4dea6872a39d80d76b5bc9740389ab05f6a5c Mon Sep 17 00:00:00 2001
From: LilyMakesThings <127533508+LilyMakesThings@users.noreply.github.com>
Date: Fri, 16 Feb 2024 16:47:45 +0000
Subject: [PATCH 041/202] Create camera.js
---
src/blocks/camera.js | 31 +++++++++++++++++++++++++++++++
1 file changed, 31 insertions(+)
create mode 100644 src/blocks/camera.js
diff --git a/src/blocks/camera.js b/src/blocks/camera.js
new file mode 100644
index 00000000000..ebc4390570f
--- /dev/null
+++ b/src/blocks/camera.js
@@ -0,0 +1,31 @@
+const Cast = require('../util/cast');
+const MathUtil = require('../util/math-util');
+const Timer = require('../util/timer');
+
+class Scratch3CameraBlocks {
+ constructor (runtime) {
+ /**
+ * The runtime instantiating this block package.
+ * @type {Runtime}
+ */
+ this.runtime = runtime;
+ }
+
+ /**
+ * Retrieve the block primitives implemented by this package.
+ * @return {object.} Mapping of opcode to Function.
+ */
+ getPrimitives () {
+ return {
+ camera_movetoxy: this.moveToXY
+ };
+ }
+
+ goToXY (args, util) {
+ const x = Cast.toNumber(args.X);
+ const y = Cast.toNumber(args.Y);
+ this.runtime.setCamera(x, y);
+ }
+}
+
+module.exports = Scratch3CameraBlocks;
From 5d507d7aec04ffbb52460dfd29c5ab0d1db76ecc Mon Sep 17 00:00:00 2001
From: LilyMakesThings <127533508+LilyMakesThings@users.noreply.github.com>
Date: Fri, 16 Feb 2024 16:48:08 +0000
Subject: [PATCH 042/202] Rename camera.js to scratch3_camera.js
---
src/blocks/{camera.js => scratch3_camera.js} | 0
1 file changed, 0 insertions(+), 0 deletions(-)
rename src/blocks/{camera.js => scratch3_camera.js} (100%)
diff --git a/src/blocks/camera.js b/src/blocks/scratch3_camera.js
similarity index 100%
rename from src/blocks/camera.js
rename to src/blocks/scratch3_camera.js
From df012145f07ba9547e38b976423164b403488510 Mon Sep 17 00:00:00 2001
From: LilyMakesThings <127533508+LilyMakesThings@users.noreply.github.com>
Date: Fri, 16 Feb 2024 16:49:11 +0000
Subject: [PATCH 043/202] Update compat-blocks.js
---
src/compiler/compat-blocks.js | 3 +++
1 file changed, 3 insertions(+)
diff --git a/src/compiler/compat-blocks.js b/src/compiler/compat-blocks.js
index 51094bedbaf..7a546fa0ec4 100644
--- a/src/compiler/compat-blocks.js
+++ b/src/compiler/compat-blocks.js
@@ -6,6 +6,9 @@
// Please keep these lists alphabetical.
const stacked = [
+ // usb to do: compile these when working
+ 'camera_movetoxy',
+
'looks_changestretchby',
'looks_hideallsprites',
'looks_say',
From b4c618281cc15e1b22bebe53e32dd9061905e52b Mon Sep 17 00:00:00 2001
From: LilyMakesThings <127533508+LilyMakesThings@users.noreply.github.com>
Date: Fri, 16 Feb 2024 16:50:31 +0000
Subject: [PATCH 044/202] Update runtime.js
---
src/engine/runtime.js | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/engine/runtime.js b/src/engine/runtime.js
index 7edd5d1dc9e..9d24b9fe7c1 100644
--- a/src/engine/runtime.js
+++ b/src/engine/runtime.js
@@ -43,6 +43,7 @@ const defaultBlockPackages = {
scratch3_operators: require('../blocks/scratch3_operators'),
scratch3_sound: require('../blocks/scratch3_sound'),
scratch3_sensing: require('../blocks/scratch3_sensing'),
+ scratch3_camera: require('../blocks/scratch3_camera'),
scratch3_data: require('../blocks/scratch3_data'),
scratch3_procedures: require('../blocks/scratch3_procedures')
};
From 0e6eef3ab0c286d26cd4b2e861f24cbafbf2fe54 Mon Sep 17 00:00:00 2001
From: LilyMakesThings <127533508+LilyMakesThings@users.noreply.github.com>
Date: Fri, 16 Feb 2024 17:22:49 +0000
Subject: [PATCH 045/202] wrong name
---
src/blocks/scratch3_camera.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/blocks/scratch3_camera.js b/src/blocks/scratch3_camera.js
index ebc4390570f..81e1b6d359d 100644
--- a/src/blocks/scratch3_camera.js
+++ b/src/blocks/scratch3_camera.js
@@ -21,7 +21,7 @@ class Scratch3CameraBlocks {
};
}
- goToXY (args, util) {
+ moveToXY (args, util) {
const x = Cast.toNumber(args.X);
const y = Cast.toNumber(args.Y);
this.runtime.setCamera(x, y);
From 53804e064115928861456a447bed4ec8f07dc86c Mon Sep 17 00:00:00 2001
From: LilyMakesThings <127533508+LilyMakesThings@users.noreply.github.com>
Date: Fri, 16 Feb 2024 18:59:58 +0000
Subject: [PATCH 046/202] Add camera blocks to vm (#18)
* Update scratch3_camera.js
* Update compat-blocks.js
---
src/blocks/scratch3_camera.js | 58 ++++++++++++++++++++++++++++++++++-
src/compiler/compat-blocks.js | 7 +++++
2 files changed, 64 insertions(+), 1 deletion(-)
diff --git a/src/blocks/scratch3_camera.js b/src/blocks/scratch3_camera.js
index 81e1b6d359d..3c3880c255a 100644
--- a/src/blocks/scratch3_camera.js
+++ b/src/blocks/scratch3_camera.js
@@ -11,13 +11,31 @@ class Scratch3CameraBlocks {
this.runtime = runtime;
}
+ getMonitored () {
+ return {
+ camera_xposition: {
+ getId: () => 'xposition'
+ },
+ camera_yposition: {
+ getId: () => 'yposition'
+ }
+ };
+ }
+
/**
* Retrieve the block primitives implemented by this package.
* @return {object.} Mapping of opcode to Function.
*/
getPrimitives () {
return {
- camera_movetoxy: this.moveToXY
+ camera_movetoxy: this.moveToXY,
+ camera_changebyxy: this.changeByXY,
+ camera_setx: this.setX,
+ camera_changex: this.changeX,
+ camera_sety: this.setY,
+ camera_changey: this.changeY,
+ camera_xposition: this.getCameraX,
+ camera_yposition: this.getCameraY
};
}
@@ -26,6 +44,44 @@ class Scratch3CameraBlocks {
const y = Cast.toNumber(args.Y);
this.runtime.setCamera(x, y);
}
+
+ changeByXY (args, util) {
+ const x = Cast.toNumber(args.X);
+ const y = Cast.toNumber(args.Y);
+ const newX = x + this.runtime.camera.x;
+ const newY = y + this.runtime.camera.y;
+ this.runtime.setCamera(x, y);
+ }
+
+ setX (args, util) {
+ const x = Cast.toNumber(args.X);
+ this.runtime.setCamera(x);
+ }
+
+ changeX (args, util) {
+ const x = Cast.toNumber(args.X);
+ const newX = x + this.runtime.camera.x;
+ this.runtime.setCamera(x);
+ }
+
+ setY (args, util) {
+ const y = Cast.toNumber(args.Y);
+ this.runtime.setCamera(null, y);
+ }
+
+ changeY (args, util) {
+ const y = Cast.toNumber(args.Y);
+ const newY = y + this.runtime.camera.y;
+ this.runtime.setCamera(null, y);
+ }
+
+ getCameraX (args, util) {
+ return this.runtime.camera.x;
+ }
+
+ getCameraY (args, util) {
+ return this.runtime.camera.y;
+ }
}
module.exports = Scratch3CameraBlocks;
diff --git a/src/compiler/compat-blocks.js b/src/compiler/compat-blocks.js
index 7a546fa0ec4..409eadeb5fe 100644
--- a/src/compiler/compat-blocks.js
+++ b/src/compiler/compat-blocks.js
@@ -8,6 +8,13 @@
const stacked = [
// usb to do: compile these when working
'camera_movetoxy',
+ 'camera_changebyxy',
+ 'camera_setx',
+ 'camera_changex',
+ 'camera_sety',
+ 'camera_changey',
+ 'camera_xposition',
+ 'camera_yposition',
'looks_changestretchby',
'looks_hideallsprites',
From d89117c88cf16c8386e23a3a4ee1589a2bd81df3 Mon Sep 17 00:00:00 2001
From: LilyMakesThings <127533508+LilyMakesThings@users.noreply.github.com>
Date: Fri, 16 Feb 2024 19:21:34 +0000
Subject: [PATCH 047/202] Fix camera in vm
---
src/blocks/scratch3_camera.js | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/blocks/scratch3_camera.js b/src/blocks/scratch3_camera.js
index 3c3880c255a..0b25153c10e 100644
--- a/src/blocks/scratch3_camera.js
+++ b/src/blocks/scratch3_camera.js
@@ -50,7 +50,7 @@ class Scratch3CameraBlocks {
const y = Cast.toNumber(args.Y);
const newX = x + this.runtime.camera.x;
const newY = y + this.runtime.camera.y;
- this.runtime.setCamera(x, y);
+ this.runtime.setCamera(newX, newY);
}
setX (args, util) {
@@ -61,7 +61,7 @@ class Scratch3CameraBlocks {
changeX (args, util) {
const x = Cast.toNumber(args.X);
const newX = x + this.runtime.camera.x;
- this.runtime.setCamera(x);
+ this.runtime.setCamera(newX);
}
setY (args, util) {
@@ -72,7 +72,7 @@ class Scratch3CameraBlocks {
changeY (args, util) {
const y = Cast.toNumber(args.Y);
const newY = y + this.runtime.camera.y;
- this.runtime.setCamera(null, y);
+ this.runtime.setCamera(null, newY);
}
getCameraX (args, util) {
From 6bf14ab318a255ad6f84a451638907634b32848a Mon Sep 17 00:00:00 2001
From: LilyMakesThings <127533508+LilyMakesThings@users.noreply.github.com>
Date: Fri, 16 Feb 2024 19:21:50 +0000
Subject: [PATCH 048/202] Update compat-blocks.js
---
src/compiler/compat-blocks.js | 2 --
1 file changed, 2 deletions(-)
diff --git a/src/compiler/compat-blocks.js b/src/compiler/compat-blocks.js
index 409eadeb5fe..b36eb2c8aff 100644
--- a/src/compiler/compat-blocks.js
+++ b/src/compiler/compat-blocks.js
@@ -13,8 +13,6 @@ const stacked = [
'camera_changex',
'camera_sety',
'camera_changey',
- 'camera_xposition',
- 'camera_yposition',
'looks_changestretchby',
'looks_hideallsprites',
From f880b1255e94515e9943522836d8745a8e72315e Mon Sep 17 00:00:00 2001
From: LilyMakesThings <127533508+LilyMakesThings@users.noreply.github.com>
Date: Fri, 16 Feb 2024 19:23:05 +0000
Subject: [PATCH 049/202] Update jsgen.js
---
src/compiler/jsgen.js | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/src/compiler/jsgen.js b/src/compiler/jsgen.js
index 0583e721478..9e30e55f8be 100644
--- a/src/compiler/jsgen.js
+++ b/src/compiler/jsgen.js
@@ -744,6 +744,11 @@ class JSGenerator {
case 'sensing.year':
return new TypedInput(`(new Date().getFullYear())`, TYPE_NUMBER);
+ case 'camera.x':
+ return new TypedInput('runtime.camera.x', TYPE_NUMBER);
+ case 'camera.y':
+ return new TypedInput('runtime.camera.y', TYPE_NUMBER);
+
case 'timer.get':
return new TypedInput('runtime.ioDevices.clock.projectTimer()', TYPE_NUMBER);
From 3a2ed58bf3af0853282de45548da7fa05f069417 Mon Sep 17 00:00:00 2001
From: LilyMakesThings <127533508+LilyMakesThings@users.noreply.github.com>
Date: Fri, 16 Feb 2024 19:23:50 +0000
Subject: [PATCH 050/202] Update irgen.js
---
src/compiler/irgen.js | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/src/compiler/irgen.js b/src/compiler/irgen.js
index f1e2c380bfd..d0e513e489c 100644
--- a/src/compiler/irgen.js
+++ b/src/compiler/irgen.js
@@ -662,6 +662,15 @@ class ScriptTreeGenerator {
kind: 'sensing.username'
};
+ case 'camera_xposition':
+ return {
+ kind: 'camera.x'
+ };
+ case 'camera_yposition':
+ return {
+ kind: 'camera.y'
+ };
+
case 'sound_sounds_menu':
// This menu is special compared to other menus -- it actually has an opcode function.
return {
From 98d5ac0ef5f5ed1956abed83273f8a5e15b2fab2 Mon Sep 17 00:00:00 2001
From: LilyMakesThings <127533508+LilyMakesThings@users.noreply.github.com>
Date: Fri, 16 Feb 2024 20:27:49 +0000
Subject: [PATCH 051/202] expose some more utils
---
src/util/usb-util.js | 23 +++++++++++++++++++++++
1 file changed, 23 insertions(+)
create mode 100644 src/util/usb-util.js
diff --git a/src/util/usb-util.js b/src/util/usb-util.js
new file mode 100644
index 00000000000..eced66e6f5a
--- /dev/null
+++ b/src/util/usb-util.js
@@ -0,0 +1,23 @@
+const Base64Util = require('../util/base64-util');
+const Cast = require('../util/cast');
+const Clone = require('../util/clone');
+const Color = require('../util/color');
+const MathUtil = require('../util/math-util');
+const StringUtil = require('../util/string-util');
+const Timer = require('../util/timer');
+const uid = require('../util/uid');
+const xmlEscape = require('../util/xml-escape');
+
+const Util = {
+ Base64Util,
+ Cast,
+ Clone,
+ Color,
+ MathUtil,
+ StringUtil,
+ Timer,
+ uid,
+ xmlEscape
+}
+
+module.exports = Util;
From 2906f32b34b097afebc256e69542a8ba340c39c3 Mon Sep 17 00:00:00 2001
From: LilyMakesThings <127533508+LilyMakesThings@users.noreply.github.com>
Date: Fri, 16 Feb 2024 20:28:37 +0000
Subject: [PATCH 052/202] Update tw-extension-api-common.js
---
src/extension-support/tw-extension-api-common.js | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/src/extension-support/tw-extension-api-common.js b/src/extension-support/tw-extension-api-common.js
index e2ac0b74d0f..dcc782ff407 100644
--- a/src/extension-support/tw-extension-api-common.js
+++ b/src/extension-support/tw-extension-api-common.js
@@ -2,12 +2,14 @@ const ArgumentType = require('./argument-type');
const BlockType = require('./block-type');
const TargetType = require('./target-type');
const Cast = require('../util/cast');
+const Util = require('../util/usb-util');
const Scratch = {
ArgumentType,
BlockType,
TargetType,
- Cast
+ Cast,
+ Util
};
module.exports = Scratch;
From ddab02ecb3c8be66f0f7779709bc8ffcf42333f7 Mon Sep 17 00:00:00 2001
From: LilyMakesThings <127533508+LilyMakesThings@users.noreply.github.com>
Date: Sat, 17 Feb 2024 00:58:35 +0000
Subject: [PATCH 053/202] Add getEffect for target
---
src/sprites/rendered-target.js | 10 ++++++++++
1 file changed, 10 insertions(+)
diff --git a/src/sprites/rendered-target.js b/src/sprites/rendered-target.js
index 26e04469f6e..263de56fda1 100644
--- a/src/sprites/rendered-target.js
+++ b/src/sprites/rendered-target.js
@@ -413,6 +413,16 @@ class RenderedTarget extends Target {
}
}
+ /**
+ * Get a particular graphic effect value.
+ * @param {!string} effectName Name of effect (see `RenderedTarget.prototype.effects`).
+ * @return {!number} Numerical magnitude of effect.
+ */
+ getEffect (effectName) { // used by compiler
+ if (!Object.prototype.hasOwnProperty.call(this.effects, effectName)) return 0;
+ return this.effects[effectName];
+ }
+
/**
* Clear all graphic effects on this rendered target.
*/
From fde332e3fe055b0864822bb1ab2416bd589435a0 Mon Sep 17 00:00:00 2001
From: LilyMakesThings <127533508+LilyMakesThings@users.noreply.github.com>
Date: Sat, 17 Feb 2024 00:59:26 +0000
Subject: [PATCH 054/202] Update scratch3_looks.js
---
src/blocks/scratch3_looks.js | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/src/blocks/scratch3_looks.js b/src/blocks/scratch3_looks.js
index bd72ab1c1dc..93e40c2bcd7 100644
--- a/src/blocks/scratch3_looks.js
+++ b/src/blocks/scratch3_looks.js
@@ -564,8 +564,7 @@ class Scratch3LooksBlocks {
getEffect (args, util) {
const effect = Cast.toString(args.EFFECT).toLowerCase();
- const value = util.target.effects[effect];
- return Cast.toNumber(value);
+ return util.target.getEffect(effect);
}
changeSize (args, util) {
From 7c0bd3d2478393f6a91f47467eae5158c4003f84 Mon Sep 17 00:00:00 2001
From: LilyMakesThings <127533508+LilyMakesThings@users.noreply.github.com>
Date: Sat, 17 Feb 2024 01:00:59 +0000
Subject: [PATCH 055/202] adjustment
---
src/compiler/irgen.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/compiler/irgen.js b/src/compiler/irgen.js
index d0e513e489c..f496b22af6b 100644
--- a/src/compiler/irgen.js
+++ b/src/compiler/irgen.js
@@ -301,7 +301,7 @@ class ScriptTreeGenerator {
};
case 'looks_effect':
return {
- kind: 'looks.effect',
+ kind: 'looks.getEffect',
effect: this.descendInputOfBlock(block, 'EFFECT')
};
From f3fc285159c280459355d0c0750fbd1e232bc431 Mon Sep 17 00:00:00 2001
From: LilyMakesThings <127533508+LilyMakesThings@users.noreply.github.com>
Date: Sat, 17 Feb 2024 01:02:19 +0000
Subject: [PATCH 056/202] Update jsgen.js
---
src/compiler/jsgen.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/compiler/jsgen.js b/src/compiler/jsgen.js
index 9e30e55f8be..f3324389e6a 100644
--- a/src/compiler/jsgen.js
+++ b/src/compiler/jsgen.js
@@ -473,7 +473,7 @@ class JSGenerator {
return new TypedInput(`${this.referenceVariable(node.list)}.value.length`, TYPE_NUMBER);
case 'looks.effect':
- return new TypedInput(`Math.round(target.effects["${sanitize(node.effect)}"] || 0)`, TYPE_NUMBER);
+ return new TypedInput(`target.getEffect(${sanitize(node.effect)})`, TYPE_NUMBER);
case 'looks.size':
return new TypedInput('Math.round(target.size)', TYPE_NUMBER);
case 'looks.backdropName':
From 20f8f5079bcf2bb6cdcd436a4a087ee3adf99b30 Mon Sep 17 00:00:00 2001
From: LilyMakesThings <127533508+LilyMakesThings@users.noreply.github.com>
Date: Sat, 17 Feb 2024 05:40:53 +0000
Subject: [PATCH 057/202] reset camera when runtime is disposed
---
src/engine/runtime.js | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/src/engine/runtime.js b/src/engine/runtime.js
index 9d24b9fe7c1..bf8e435c4e6 100644
--- a/src/engine/runtime.js
+++ b/src/engine/runtime.js
@@ -2402,6 +2402,13 @@ class Runtime extends EventEmitter {
this.ioDevices.cloud.clear();
+ this.camera = {
+ x: Runtime.CAMERA_X,
+ y: Runtime.CAMERA_Y,
+ direction: Runtime.CAMERA_DIRECTION,
+ zoom: Runtime.CAMERA_ZOOM
+ }
+
// Reset runtime cloud data info
const newCloudDataManager = cloudDataManager(this.cloudOptions);
this.hasCloudData = newCloudDataManager.hasCloudVariables;
From 3f80f949b699b355b9281d0d628a64d61f7dd5fb Mon Sep 17 00:00:00 2001
From: LilyMakesThings <127533508+LilyMakesThings@users.noreply.github.com>
Date: Sat, 17 Feb 2024 06:15:43 +0000
Subject: [PATCH 058/202] Add interpolation to the camera (#19)
---
src/engine/runtime.js | 20 +++++++++------
src/engine/tw-interpolate.js | 47 +++++++++++++++++++++++++++++++++++-
2 files changed, 58 insertions(+), 9 deletions(-)
diff --git a/src/engine/runtime.js b/src/engine/runtime.js
index bf8e435c4e6..26f6329119b 100644
--- a/src/engine/runtime.js
+++ b/src/engine/runtime.js
@@ -476,7 +476,9 @@ class Runtime extends EventEmitter {
x: Runtime.CAMERA_X,
y: Runtime.CAMERA_Y,
direction: Runtime.CAMERA_DIRECTION,
- zoom: Runtime.CAMERA_ZOOM
+ zoom: Runtime.CAMERA_ZOOM,
+ enabled: true,
+ interpolationData: null
}
this.runtimeOptions = {
@@ -2389,6 +2391,15 @@ class Runtime extends EventEmitter {
this.emit(Runtime.RUNTIME_DISPOSED);
this.ioDevices.clock.resetProjectTimer();
this.fontManager.clear();
+
+ this.camera = {
+ x: Runtime.CAMERA_X,
+ y: Runtime.CAMERA_Y,
+ direction: Runtime.CAMERA_DIRECTION,
+ zoom: Runtime.CAMERA_ZOOM,
+ enabled: true,
+ interpolationData: null
+ }
// @todo clear out extensions? turboMode? etc.
// *********** Cloud *******************
@@ -2402,13 +2413,6 @@ class Runtime extends EventEmitter {
this.ioDevices.cloud.clear();
- this.camera = {
- x: Runtime.CAMERA_X,
- y: Runtime.CAMERA_Y,
- direction: Runtime.CAMERA_DIRECTION,
- zoom: Runtime.CAMERA_ZOOM
- }
-
// Reset runtime cloud data info
const newCloudDataManager = cloudDataManager(this.cloudOptions);
this.hasCloudData = newCloudDataManager.hasCloudVariables;
diff --git a/src/engine/tw-interpolate.js b/src/engine/tw-interpolate.js
index 4396c79df4b..17310dc66f9 100644
--- a/src/engine/tw-interpolate.js
+++ b/src/engine/tw-interpolate.js
@@ -4,7 +4,31 @@
*/
const setupInitialState = runtime => {
const renderer = runtime.renderer;
+ const camera = runtime.camera;
+ // camera state
+ if (camera.enabled) {
+ // x, y, direction, zoom
+ if (renderer && camera.interpolationData) {
+ renderer._updateCamera(
+ camera.x,
+ camera.y,
+ camera.direction,
+ camera.zoom
+ );
+ }
+
+ camera.interpolationData = {
+ x: camera.x,
+ y: camera.y,
+ direction: camera.direction,
+ zoom: camera.zoom
+ }
+ } else {
+ camera.interpolationData = null;
+ }
+
+ // target state(s)
for (const target of runtime.targets) {
const directionAndScale = target._getRenderedDirectionAndScale();
@@ -16,7 +40,7 @@ const setupInitialState = runtime => {
renderer.updateDrawableEffect(drawableID, 'ghost', target.effects.ghost);
}
- if (target.visible && !target.isStage) {
+ if (target.visible && !target.isStage && target.interpolation) {
target.interpolationData = {
x: target.x,
y: target.y,
@@ -42,6 +66,27 @@ const interpolate = (runtime, time) => {
return;
}
+ // camera state
+ if (camera.enabled && camera.interpolationData) {
+ const interpolationData = camera.interpolationData;
+
+ // Position interpolation.
+ const xDistance = camera.x - interpolationData.x;
+ const yDistance = camera.y - interpolationData.y;
+ const absoluteXDistance = Math.abs(xDistance);
+ const absoluteYDistance = Math.abs(yDistance);
+ if (absoluteXDistance > 0.1 || absoluteYDistance > 0.1) {
+ // Large movements are likely intended to be instantaneous.
+ const distance = Math.sqrt((absoluteXDistance ** 2) + (absoluteYDistance ** 2));
+ if (distance < 50) {
+ const newX = interpolationData.x + (xDistance * time);
+ const newY = interpolationData.y + (yDistance * time);
+ renderer._updateCamera(newX, newY, camera.direction, camera.zoom);
+ }
+ }
+ }
+
+ // target state(s)
for (const target of runtime.targets) {
// interpolationData is the initial state at the start of the frame (time 0)
// the state on the target itself is the state at the end of the frame (time 1)
From e0ddba2dd23608595418929257a032e79afa5557 Mon Sep 17 00:00:00 2001
From: LilyMakesThings <127533508+LilyMakesThings@users.noreply.github.com>
Date: Sat, 17 Feb 2024 06:16:43 +0000
Subject: [PATCH 059/202] define camera
---
src/engine/tw-interpolate.js | 2 ++
1 file changed, 2 insertions(+)
diff --git a/src/engine/tw-interpolate.js b/src/engine/tw-interpolate.js
index 17310dc66f9..bdb26fe3251 100644
--- a/src/engine/tw-interpolate.js
+++ b/src/engine/tw-interpolate.js
@@ -62,6 +62,8 @@ const setupInitialState = runtime => {
*/
const interpolate = (runtime, time) => {
const renderer = runtime.renderer;
+ const camera = runtime.camera;
+
if (!renderer) {
return;
}
From 974b57af567d7214c41e06e0b75852db7b6f6ea7 Mon Sep 17 00:00:00 2001
From: LilyMakesThings <127533508+LilyMakesThings@users.noreply.github.com>
Date: Sat, 17 Feb 2024 19:00:02 +0000
Subject: [PATCH 060/202] Rewrite camera (#20)
---
src/blocks/scratch3_camera.js | 18 +++--
src/compiler/jsgen.js | 2 +-
src/engine/camera.js | 136 +++++++++++++++++++++++++++++++++
src/engine/runtime.js | 99 ++----------------------
src/engine/tw-interpolate.js | 7 +-
src/sprites/rendered-target.js | 8 --
src/virtual-machine.js | 7 --
7 files changed, 156 insertions(+), 121 deletions(-)
create mode 100644 src/engine/camera.js
diff --git a/src/blocks/scratch3_camera.js b/src/blocks/scratch3_camera.js
index 0b25153c10e..b010cb50c70 100644
--- a/src/blocks/scratch3_camera.js
+++ b/src/blocks/scratch3_camera.js
@@ -9,6 +9,8 @@ class Scratch3CameraBlocks {
* @type {Runtime}
*/
this.runtime = runtime;
+
+ this.camera = runtime.camera;
}
getMonitored () {
@@ -42,7 +44,7 @@ class Scratch3CameraBlocks {
moveToXY (args, util) {
const x = Cast.toNumber(args.X);
const y = Cast.toNumber(args.Y);
- this.runtime.setCamera(x, y);
+ this.camera.setXY(x, y);
}
changeByXY (args, util) {
@@ -50,37 +52,37 @@ class Scratch3CameraBlocks {
const y = Cast.toNumber(args.Y);
const newX = x + this.runtime.camera.x;
const newY = y + this.runtime.camera.y;
- this.runtime.setCamera(newX, newY);
+ this.camera.setXY(newX, newY);
}
setX (args, util) {
const x = Cast.toNumber(args.X);
- this.runtime.setCamera(x);
+ this.camera.setXY(x, this.camera.y);
}
changeX (args, util) {
const x = Cast.toNumber(args.X);
const newX = x + this.runtime.camera.x;
- this.runtime.setCamera(newX);
+ this.camera.setXY(newX, this.camera.y);
}
setY (args, util) {
const y = Cast.toNumber(args.Y);
- this.runtime.setCamera(null, y);
+ this.camera.setXY(this.camera.x, y);
}
changeY (args, util) {
const y = Cast.toNumber(args.Y);
const newY = y + this.runtime.camera.y;
- this.runtime.setCamera(null, newY);
+ this.camera.setXY(this.camera.x, newY);
}
getCameraX (args, util) {
- return this.runtime.camera.x;
+ return this.camera.x;
}
getCameraY (args, util) {
- return this.runtime.camera.y;
+ return this.camera.y;
}
}
diff --git a/src/compiler/jsgen.js b/src/compiler/jsgen.js
index f3324389e6a..49ee53383fe 100644
--- a/src/compiler/jsgen.js
+++ b/src/compiler/jsgen.js
@@ -472,7 +472,7 @@ class JSGenerator {
case 'list.length':
return new TypedInput(`${this.referenceVariable(node.list)}.value.length`, TYPE_NUMBER);
- case 'looks.effect':
+ case 'looks.getEffect':
return new TypedInput(`target.getEffect(${sanitize(node.effect)})`, TYPE_NUMBER);
case 'looks.size':
return new TypedInput('Math.round(target.size)', TYPE_NUMBER);
diff --git a/src/engine/camera.js b/src/engine/camera.js
new file mode 100644
index 00000000000..2fb7e8759c3
--- /dev/null
+++ b/src/engine/camera.js
@@ -0,0 +1,136 @@
+const Cast = require('../util/cast');
+const MathUtil = require('../util/math-util');
+
+/**
+ * @fileoverview
+ * The camera is an arbitrary object used to
+ * describe properties of the renderer projection.
+ */
+
+/**
+ * Camera: instance of a camera object on the stage.
+ */
+class Camera {
+ constructor(runtime) {
+ this.runtime = runtime;
+
+ this.renderer = runtime.renderer;
+
+ /**
+ * Scratch X coordinate. Currently should range from -240 to 240.
+ * @type {Number}
+ */
+ this.x = 0;
+
+ /**
+ * Scratch Y coordinate. Currently should range from -180 to 180.
+ * @type {number}
+ */
+ this.y = 0;
+
+ /**
+ * Scratch direction. Currently should range from -179 to 180.
+ * @type {number}
+ */
+ this.direction = 90;
+
+ /**
+ * Zoom of camera as a percentage. Similar to size.
+ * @type {number}
+ */
+ this.zoom = 100;
+
+ /**
+ * Determines whether the camera values will affect the projection.
+ * @type {boolean}
+ */
+ this.enabled = true;
+
+ /**
+ * Interpolation data used by tw-interpolate.
+ */
+ this.interpolationData = null;
+ }
+
+ /**
+ * Event name for the camera updating.
+ * @const {string}
+ */
+ static get CAMERA_NEEDS_UPDATE () {
+ return 'CAMERA_NEEDS_UPDATE';
+ }
+
+ /**
+ * Set the X and Y values of the camera.
+ * @param x The x coordinate.
+ * @param y The y coordinate.
+ */
+ setXY(x, y) {
+ this.x = Cast.toNumber(x);
+ this.y = Cast.toNumber(y);
+
+ this.emitCameraUpdate();
+ }
+
+ /**
+ * Set the zoom of the camera.
+ * @param zoom The new zoom value.
+ */
+ setZoom(zoom) {
+ this.zoom = Cast.toNumber(zoom);
+ if (this.runtime.runtimeOptions.miscLimits) {
+ this.zoom = MathUtil.clamp(this.zoom, 10, 300);
+ }
+
+ this.emitCameraUpdate();
+ }
+
+ /**
+ * Point the camera towards a given direction.
+ * @param direction Direction to point the camera.
+ */
+ setDirection(direction) {
+ if (!isFinite(direction)) return;
+
+ this.direction = MathUtil.wrapClamp(direction, -179, 180);
+
+ this.emitCameraUpdate();
+ }
+
+ /**
+ * Set whether the camera will affect the projection.
+ * @param enabled The new enabled state.
+ */
+ setEnabled(enabled) {
+ this.enabled = enabled;
+ }
+
+ /**
+ * Tell the renderer to update the rendered camera state.
+ */
+ emitCameraUpdate() {
+ if (!this.renderer) return;
+
+ this.renderer._updateCamera(
+ this.x,
+ this.y,
+ this.direction,
+ this.zoom
+ )
+
+ this.emit(Camera.CAMERA_UPDATE);
+ }
+
+ /**
+ * Reset all camera properties.
+ */
+ reset() {
+ this.x = 0;
+ this.y = 0;
+ this.direction = 90;
+ this.zoom = 100;
+ this.enabled = true;
+ }
+}
+
+module.exports = Camera;
diff --git a/src/engine/runtime.js b/src/engine/runtime.js
index 26f6329119b..79622071432 100644
--- a/src/engine/runtime.js
+++ b/src/engine/runtime.js
@@ -50,6 +50,7 @@ const defaultBlockPackages = {
const interpolate = require('./tw-interpolate');
const FrameLoop = require('./tw-frame-loop');
+const Camera = require('./camera');
const defaultExtensionColors = ['#0FBD8C', '#0DA57A', '#0B8E69'];
@@ -472,14 +473,7 @@ class Runtime extends EventEmitter {
this.stageWidth = Runtime.STAGE_WIDTH;
this.stageHeight = Runtime.STAGE_HEIGHT;
- this.camera = {
- x: Runtime.CAMERA_X,
- y: Runtime.CAMERA_Y,
- direction: Runtime.CAMERA_DIRECTION,
- zoom: Runtime.CAMERA_ZOOM,
- enabled: true,
- interpolationData: null
- }
+ this.camera = new Camera(this);
this.runtimeOptions = {
maxClones: Runtime.MAX_CLONES,
@@ -579,42 +573,6 @@ class Runtime extends EventEmitter {
return 360;
}
- /**
- * X position of camera.
- * @const {number}
- */
- static get CAMERA_X () {
- // tw: camera is set per-runtime, this is only the initial value
- return 0;
- }
-
- /**
- * Initial rotation of camera.
- * @const {number}
- */
- static get CAMERA_DIRECTION () {
- // tw: camera is set per-runtime, this is only the initial value
- return 90;
- }
-
- /**
- * Initial zoom of camera.
- * @const {number}
- */
- static get CAMERA_ZOOM () {
- // tw: camera is set per-runtime, this is only the initial value
- return 100;
- }
-
- /**
- * Y position of camera.
- * @const {number}
- */
- static get CAMERA_Y () {
- // tw: camera is set per-runtime, this is only the initial value
- return 0;
- }
-
/**
* Event name for glowing a script.
* @const {string}
@@ -712,14 +670,6 @@ class Runtime extends EventEmitter {
return 'STAGE_SIZE_CHANGED';
}
- /**
- * Event name for camera moving.
- * @const {string}
- */
- static get CAMREA_NEEDS_UPDATE () {
- return 'CAMERA_NEEDS_UPDATE';
- }
-
/**
* Event name for compiler errors.
* @const {string}
@@ -2392,14 +2342,7 @@ class Runtime extends EventEmitter {
this.ioDevices.clock.resetProjectTimer();
this.fontManager.clear();
- this.camera = {
- x: Runtime.CAMERA_X,
- y: Runtime.CAMERA_Y,
- direction: Runtime.CAMERA_DIRECTION,
- zoom: Runtime.CAMERA_ZOOM,
- enabled: true,
- interpolationData: null
- }
+ this.camera.reset();
// @todo clear out extensions? turboMode? etc.
// *********** Cloud *******************
@@ -2842,40 +2785,10 @@ class Runtime extends EventEmitter {
}
/**
- * Change all values of the camera. This will also inform the renderer of the new camera position.
- * @param {number} x New camera x
- * @param {number} y New camera y
- * @param {number} direction New camera direction
- * @param {number} zoom New camera zoom
+ * Get the current instance of the camera.
*/
- setCamera(x, y, direction, zoom) {
- if (
- this.camera.x !== x ||
- this.camera.y !== y ||
- this.camera.direction !== direction ||
- this.camera.zoom !== zoom
- ) {
- this.camera.x = x ?? this.camera.x;
- this.camera.y = y ?? this.camera.y;
- this.camera.direction = direction ?? this.camera.direction;
- this.camera.zoom = zoom ?? this.camera.zoom;
-
- if (this.renderer) {
- this.renderer._updateCamera (
- this.camera.x,
- this.camera.y,
- this.camera.direction,
- this.camera.zoom,
- );
- }
- }
- this.emit(
- Runtime.CAMERA_NEEDS_UPDATE,
- this.camera.x,
- this.camera.y,
- this.camera.direction,
- this.camera.zoom
- );
+ getCamera() {
+ return this.camera;
}
// eslint-disable-next-line no-unused-vars
diff --git a/src/engine/tw-interpolate.js b/src/engine/tw-interpolate.js
index bdb26fe3251..bf63f2d079b 100644
--- a/src/engine/tw-interpolate.js
+++ b/src/engine/tw-interpolate.js
@@ -17,7 +17,7 @@ const setupInitialState = runtime => {
camera.zoom
);
}
-
+
camera.interpolationData = {
x: camera.x,
y: camera.y,
@@ -40,7 +40,7 @@ const setupInitialState = runtime => {
renderer.updateDrawableEffect(drawableID, 'ghost', target.effects.ghost);
}
- if (target.visible && !target.isStage && target.interpolation) {
+ if (target.visible && !target.isStage) {
target.interpolationData = {
x: target.x,
y: target.y,
@@ -93,8 +93,7 @@ const interpolate = (runtime, time) => {
// interpolationData is the initial state at the start of the frame (time 0)
// the state on the target itself is the state at the end of the frame (time 1)
const interpolationData = target.interpolationData;
- const interpolationEnabled = target.interpolation;
- if (!interpolationData || !interpolationEnabled) {
+ if (!interpolationData) {
continue;
}
diff --git a/src/sprites/rendered-target.js b/src/sprites/rendered-target.js
index 263de56fda1..d64e47a649b 100644
--- a/src/sprites/rendered-target.js
+++ b/src/sprites/rendered-target.js
@@ -167,7 +167,6 @@ class RenderedTarget extends Target {
this.onTargetMoved = null;
this.onTargetVisualChange = null;
- this.interpolation = false;
this.interpolationData = null;
}
@@ -1077,13 +1076,6 @@ class RenderedTarget extends Target {
this.dragging = false;
}
- /**
- * Set whether the sprite is interpolated.
- */
- setInterpolation(interpolated) {
- this.interpolation = interpolated;
- }
-
/**
* Serialize sprite info, used when emitting events about the sprite
* @returns {object} Sprite data as a simple object
diff --git a/src/virtual-machine.js b/src/virtual-machine.js
index 360c671021e..baf0cf67fcd 100644
--- a/src/virtual-machine.js
+++ b/src/virtual-machine.js
@@ -187,9 +187,6 @@ class VirtualMachine extends EventEmitter {
this.runtime.on(Runtime.STAGE_SIZE_CHANGED, (width, height) => {
this.emit(Runtime.STAGE_SIZE_CHANGED, width, height);
});
- this.runtime.on(Runtime.CAMERA_NEEDS_UPDATE, (x, y, direction, zoom) => {
- this.emit(Runtime.CAMERA_NEEDS_UPDATE, x, y, direction, zoom);
- });
this.runtime.on(Runtime.COMPILE_ERROR, (target, error) => {
this.emit(Runtime.COMPILE_ERROR, target, error);
});
@@ -309,10 +306,6 @@ class VirtualMachine extends EventEmitter {
this.runtime.setStageSize(width, height);
}
- setCamera (x, y, direction, zoom) {
- this.runtime.setCamera(x, y, direction, zoom);
- }
-
setInEditor (inEditor) {
this.runtime.setInEditor(inEditor);
}
From fda91734ee6adf33cd0f70c053d1adb17ddf9be2 Mon Sep 17 00:00:00 2001
From: LilyMakesThings <127533508+LilyMakesThings@users.noreply.github.com>
Date: Sat, 17 Feb 2024 19:01:28 +0000
Subject: [PATCH 061/202] oops
---
src/engine/camera.js | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/engine/camera.js b/src/engine/camera.js
index 2fb7e8759c3..c4901ffc137 100644
--- a/src/engine/camera.js
+++ b/src/engine/camera.js
@@ -56,8 +56,8 @@ class Camera {
* Event name for the camera updating.
* @const {string}
*/
- static get CAMERA_NEEDS_UPDATE () {
- return 'CAMERA_NEEDS_UPDATE';
+ static get CAMERA_UPDATE () {
+ return 'CAMERA_UPDATE';
}
/**
From 4e49a061ca549d2cb972934f1e837d584e3af438 Mon Sep 17 00:00:00 2001
From: LilyMakesThings <127533508+LilyMakesThings@users.noreply.github.com>
Date: Sat, 17 Feb 2024 20:44:21 +0000
Subject: [PATCH 062/202] Next batch (#21)
* Update scratch3_camera.js
* Update camera.js
* Update jsgen.js
* Update irgen.js
* Update compat-blocks.js
---
src/blocks/scratch3_camera.js | 18 ++++++++----------
src/compiler/compat-blocks.js | 1 +
src/compiler/irgen.js | 5 -----
src/compiler/jsgen.js | 2 --
src/engine/camera.js | 12 +++++-------
5 files changed, 14 insertions(+), 24 deletions(-)
diff --git a/src/blocks/scratch3_camera.js b/src/blocks/scratch3_camera.js
index b010cb50c70..29fe16def24 100644
--- a/src/blocks/scratch3_camera.js
+++ b/src/blocks/scratch3_camera.js
@@ -9,8 +9,6 @@ class Scratch3CameraBlocks {
* @type {Runtime}
*/
this.runtime = runtime;
-
- this.camera = runtime.camera;
}
getMonitored () {
@@ -44,7 +42,7 @@ class Scratch3CameraBlocks {
moveToXY (args, util) {
const x = Cast.toNumber(args.X);
const y = Cast.toNumber(args.Y);
- this.camera.setXY(x, y);
+ this.runtime.camera.setXY(x, y);
}
changeByXY (args, util) {
@@ -52,37 +50,37 @@ class Scratch3CameraBlocks {
const y = Cast.toNumber(args.Y);
const newX = x + this.runtime.camera.x;
const newY = y + this.runtime.camera.y;
- this.camera.setXY(newX, newY);
+ this.runtime.camera.setXY(newX, newY);
}
setX (args, util) {
const x = Cast.toNumber(args.X);
- this.camera.setXY(x, this.camera.y);
+ this.runtime.camera.setXY(x, this.runtime.camera.y);
}
changeX (args, util) {
const x = Cast.toNumber(args.X);
const newX = x + this.runtime.camera.x;
- this.camera.setXY(newX, this.camera.y);
+ this.runtime.camera.setXY(newX, this.runtime.camera.y);
}
setY (args, util) {
const y = Cast.toNumber(args.Y);
- this.camera.setXY(this.camera.x, y);
+ this.runtime.camera.setXY(this.runtime.camera.x, y);
}
changeY (args, util) {
const y = Cast.toNumber(args.Y);
const newY = y + this.runtime.camera.y;
- this.camera.setXY(this.camera.x, newY);
+ this.runtime.camera.setXY(this.runtime.camera.x, newY);
}
getCameraX (args, util) {
- return this.camera.x;
+ return this.runtime.camera.x;
}
getCameraY (args, util) {
- return this.camera.y;
+ return this.runtime.camera.y;
}
}
diff --git a/src/compiler/compat-blocks.js b/src/compiler/compat-blocks.js
index b36eb2c8aff..26c6ec44424 100644
--- a/src/compiler/compat-blocks.js
+++ b/src/compiler/compat-blocks.js
@@ -44,6 +44,7 @@ const stacked = [
];
const inputs = [
+ 'looks_effect',
'motion_xscroll',
'motion_yscroll',
'sensing_loud',
diff --git a/src/compiler/irgen.js b/src/compiler/irgen.js
index f496b22af6b..5aae5cb1e1b 100644
--- a/src/compiler/irgen.js
+++ b/src/compiler/irgen.js
@@ -299,11 +299,6 @@ class ScriptTreeGenerator {
return {
kind: 'looks.size'
};
- case 'looks_effect':
- return {
- kind: 'looks.getEffect',
- effect: this.descendInputOfBlock(block, 'EFFECT')
- };
case 'motion_rotationstyle':
return {
diff --git a/src/compiler/jsgen.js b/src/compiler/jsgen.js
index 49ee53383fe..85e6e56963d 100644
--- a/src/compiler/jsgen.js
+++ b/src/compiler/jsgen.js
@@ -472,8 +472,6 @@ class JSGenerator {
case 'list.length':
return new TypedInput(`${this.referenceVariable(node.list)}.value.length`, TYPE_NUMBER);
- case 'looks.getEffect':
- return new TypedInput(`target.getEffect(${sanitize(node.effect)})`, TYPE_NUMBER);
case 'looks.size':
return new TypedInput('Math.round(target.size)', TYPE_NUMBER);
case 'looks.backdropName':
diff --git a/src/engine/camera.js b/src/engine/camera.js
index c4901ffc137..64cfddadfb2 100644
--- a/src/engine/camera.js
+++ b/src/engine/camera.js
@@ -1,3 +1,5 @@
+const EventEmitter = require('events');
+
const Cast = require('../util/cast');
const MathUtil = require('../util/math-util');
@@ -10,12 +12,10 @@ const MathUtil = require('../util/math-util');
/**
* Camera: instance of a camera object on the stage.
*/
-class Camera {
+class Camera extends EventEmitter {
constructor(runtime) {
this.runtime = runtime;
- this.renderer = runtime.renderer;
-
/**
* Scratch X coordinate. Currently should range from -240 to 240.
* @type {Number}
@@ -109,16 +109,14 @@ class Camera {
* Tell the renderer to update the rendered camera state.
*/
emitCameraUpdate() {
- if (!this.renderer) return;
+ if (!this.runtime.renderer) return;
- this.renderer._updateCamera(
+ this.runtime.renderer._updateCamera(
this.x,
this.y,
this.direction,
this.zoom
)
-
- this.emit(Camera.CAMERA_UPDATE);
}
/**
From 82ea6c446c65b4c0193e1e8ca5fca5b495703294 Mon Sep 17 00:00:00 2001
From: LilyMakesThings <127533508+LilyMakesThings@users.noreply.github.com>
Date: Sat, 17 Feb 2024 20:51:25 +0000
Subject: [PATCH 063/202] call super
---
src/engine/camera.js | 2 ++
1 file changed, 2 insertions(+)
diff --git a/src/engine/camera.js b/src/engine/camera.js
index 64cfddadfb2..2b8a93fc3f0 100644
--- a/src/engine/camera.js
+++ b/src/engine/camera.js
@@ -14,6 +14,8 @@ const MathUtil = require('../util/math-util');
*/
class Camera extends EventEmitter {
constructor(runtime) {
+ super();
+
this.runtime = runtime;
/**
From d937b28627c96189ce2492e1ba6525f05a602f89 Mon Sep 17 00:00:00 2001
From: LilyMakesThings <127533508+LilyMakesThings@users.noreply.github.com>
Date: Sat, 17 Feb 2024 21:36:59 +0000
Subject: [PATCH 064/202] Wrap clamp the color effect value (#22)
* Update rendered-target.js
* Update scratch3_looks.js
---
src/blocks/scratch3_looks.js | 3 +++
src/sprites/rendered-target.js | 6 ++++++
2 files changed, 9 insertions(+)
diff --git a/src/blocks/scratch3_looks.js b/src/blocks/scratch3_looks.js
index 93e40c2bcd7..cf2833e20fa 100644
--- a/src/blocks/scratch3_looks.js
+++ b/src/blocks/scratch3_looks.js
@@ -314,6 +314,9 @@ class Scratch3LooksBlocks {
getMonitored () {
return {
+ looks_effect: {
+ getId: (targetId, fields) => getMonitorIdForBlockWithArgs(`${targetId}_effect`, fields)
+ },
looks_size: {
isSpriteSpecific: true,
getId: targetId => `${targetId}_size`
diff --git a/src/sprites/rendered-target.js b/src/sprites/rendered-target.js
index d64e47a649b..ff22191a4c2 100644
--- a/src/sprites/rendered-target.js
+++ b/src/sprites/rendered-target.js
@@ -402,6 +402,12 @@ class RenderedTarget extends Target {
*/
setEffect (effectName, value) { // used by compiler
if (!Object.prototype.hasOwnProperty.call(this.effects, effectName)) return;
+
+ // "Infinity" is often used to get a monochrome effect.
+ if (effectName === 'color' && isFinite(value) {
+ value = MathUtil.wrapClamp(value, 0, 200);
+ }
+
this.effects[effectName] = value;
if (this.renderer) {
this.renderer.updateDrawableEffect(this.drawableID, effectName, value);
From dd45612a80af5d9c9006b452c809ed5eaca8ebe6 Mon Sep 17 00:00:00 2001
From: LilyMakesThings <127533508+LilyMakesThings@users.noreply.github.com>
Date: Sun, 18 Feb 2024 02:00:24 +0000
Subject: [PATCH 065/202] fix typo
---
src/sprites/rendered-target.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/sprites/rendered-target.js b/src/sprites/rendered-target.js
index ff22191a4c2..eab59ca75bf 100644
--- a/src/sprites/rendered-target.js
+++ b/src/sprites/rendered-target.js
@@ -404,7 +404,7 @@ class RenderedTarget extends Target {
if (!Object.prototype.hasOwnProperty.call(this.effects, effectName)) return;
// "Infinity" is often used to get a monochrome effect.
- if (effectName === 'color' && isFinite(value) {
+ if (effectName === 'color' && isFinite(value)) {
value = MathUtil.wrapClamp(value, 0, 200);
}
From 5e636282e9e00f5d1c2f3ee50cfef3927e8dbc66 Mon Sep 17 00:00:00 2001
From: LilyMakesThings <127533508+LilyMakesThings@users.noreply.github.com>
Date: Sun, 18 Feb 2024 20:03:57 +0000
Subject: [PATCH 066/202] Next batch (#23)
* request redraw when the camera is moved
* mark looks_effect as sprite-specific
* transform _scratchX/Y instead of getScratchX/Y
This'll fix the mouse position addon
* transform "goTo"'s fencing in relation to the camera
---
src/blocks/scratch3_looks.js | 1 +
src/blocks/scratch3_motion.js | 8 ++++++++
src/engine/camera.js | 2 ++
src/io/mouse.js | 23 ++++++++++++-----------
4 files changed, 23 insertions(+), 11 deletions(-)
diff --git a/src/blocks/scratch3_looks.js b/src/blocks/scratch3_looks.js
index cf2833e20fa..9e2ca4d6f3f 100644
--- a/src/blocks/scratch3_looks.js
+++ b/src/blocks/scratch3_looks.js
@@ -315,6 +315,7 @@ class Scratch3LooksBlocks {
getMonitored () {
return {
looks_effect: {
+ isSpriteSpecific: true,
getId: (targetId, fields) => getMonitorIdForBlockWithArgs(`${targetId}_effect`, fields)
},
looks_size: {
diff --git a/src/blocks/scratch3_motion.js b/src/blocks/scratch3_motion.js
index 9a962dd0e70..ab2833fa5a7 100644
--- a/src/blocks/scratch3_motion.js
+++ b/src/blocks/scratch3_motion.js
@@ -102,6 +102,14 @@ class Scratch3MotionBlocks {
const stageHeight = this.runtime.stageHeight;
targetX = Math.round(stageWidth * (Math.random() - 0.5));
targetY = Math.round(stageHeight * (Math.random() - 0.5));
+
+ // usb: transform based on camera
+ targetX = this.runtime.renderer.translateX(
+ targetX, false, 1, true, targetY, 1
+ );
+ targetY = this.runtime.renderer.translateY(
+ targetY, false, 1, true, targetX, 1
+ );
} else {
targetName = Cast.toString(targetName);
const goToTarget = this.runtime.getSpriteTargetByName(targetName);
diff --git a/src/engine/camera.js b/src/engine/camera.js
index 2b8a93fc3f0..3f35004cb6e 100644
--- a/src/engine/camera.js
+++ b/src/engine/camera.js
@@ -119,6 +119,8 @@ class Camera extends EventEmitter {
this.direction,
this.zoom
)
+
+ this.runtime.requestRedraw();
}
/**
diff --git a/src/io/mouse.js b/src/io/mouse.js
index 53264f52e55..c88e20e7cc3 100644
--- a/src/io/mouse.js
+++ b/src/io/mouse.js
@@ -64,13 +64,17 @@ class Mouse {
*/
postData (data) {
if (typeof data.x === 'number') {
- // usb: transform based on camera
this._clientX = data.x;
this._scratchX = MathUtil.clamp(
this.runtime.stageWidth * ((data.x / data.canvasWidth) - 0.5),
-(this.runtime.stageWidth / 2),
(this.runtime.stageWidth / 2)
);
+
+ // usb: transform based on camera
+ this._scratchX = this.runtime.renderer.translateX(
+ this._scratchX, false, 1, true, this._scratchY, 1
+ );
}
if (typeof data.y === 'number') {
this._clientY = data.y;
@@ -79,6 +83,11 @@ class Mouse {
-(this.runtime.stageHeight / 2),
(this.runtime.stageHeight / 2)
);
+
+ // usb: transform based on camera
+ this._scratchY = this.runtime.renderer.translateY(
+ this._scratchY, false, 1, true, this._scratchX, 1
+ );
}
if (typeof data.isDown !== 'undefined') {
// If no button specified, default to left button for compatibility
@@ -141,11 +150,7 @@ class Mouse {
* @return {number} Clamped and integer rounded X position of the mouse cursor.
*/
getScratchX () {
- const x = this.roundValue(this._scratchX);
- const y = this.roundValue(this._scratchY);
- return this.runtime.renderer.translateX(
- x, false, 1, true, y, 1
- );
+ return this.roundValue(this._scratchX);
}
/**
@@ -153,11 +158,7 @@ class Mouse {
* @return {number} Clamped and integer rounded Y position of the mouse cursor.
*/
getScratchY () {
- const x = this.roundValue(this._scratchX);
- const y = this.roundValue(this._scratchY);
- return this.runtime.renderer.translateY(
- y, false, 1, true, x, 1
- );
+ return this.roundValue(this._scratchY);
}
/**
From 426634740acd06f01cbb63ff1ad4cc061ef5b6c8 Mon Sep 17 00:00:00 2001
From: LilyMakesThings <127533508+LilyMakesThings@users.noreply.github.com>
Date: Wed, 21 Feb 2024 18:52:14 +0000
Subject: [PATCH 067/202] ArgumentType.LABEL (#8)
---
src/engine/runtime.js | 7 +++++++
src/extension-support/argument-type.js | 7 ++++++-
2 files changed, 13 insertions(+), 1 deletion(-)
diff --git a/src/engine/runtime.js b/src/engine/runtime.js
index 79622071432..36b423ff092 100644
--- a/src/engine/runtime.js
+++ b/src/engine/runtime.js
@@ -129,6 +129,10 @@ const ArgumentTypeMap = (() => {
fieldType: 'field_variable',
fieldName: 'VARIABLE'
};
+ map[ArgumentType.LABEL] = {
+ fieldType: 'field_label_serializable',
+ fieldName: 'LABEL'
+ };
return map;
})();
@@ -1662,6 +1666,9 @@ class Runtime extends EventEmitter {
// check if this is not one of those cases. E.g. an inline image on a block.
if (argTypeInfo.fieldType === 'field_image') {
argJSON = this._constructInlineImageJson(argInfo);
+ } else if (argTypeInfo.fieldType === 'field_label') {
+ argJSON.type = 'field_label';
+ argJSON.text = argInfo.text;
} else if (argTypeInfo.fieldType === 'field_variable') {
argJSON = this._constructVariableJson(argInfo, placeholder);
} else {
diff --git a/src/extension-support/argument-type.js b/src/extension-support/argument-type.js
index bf7fbdabb41..f0f9ecbe279 100644
--- a/src/extension-support/argument-type.js
+++ b/src/extension-support/argument-type.js
@@ -56,7 +56,12 @@ const ArgumentType = {
/**
* Name of variable in the current specified target(s)
*/
- VARIABLE: 'variable'
+ VARIABLE: 'variable',
+
+ /**
+ * A label text that can be dynamically changed
+ */
+ LABEL: 'label'
};
module.exports = ArgumentType;
From 36175825b4151d1560be30ea2023cb603e9dbe53 Mon Sep 17 00:00:00 2001
From: LilyMakesThings <127533508+LilyMakesThings@users.noreply.github.com>
Date: Thu, 22 Feb 2024 07:45:01 +0000
Subject: [PATCH 068/202] Hat parameters (#24)
---
src/compiler/irgen.js | 16 ++++++++++++----
src/compiler/jsgen.js | 3 +++
src/engine/block-utility.js | 5 +++--
src/engine/runtime.js | 25 +++++++++++++++++++++++--
src/extension-support/argument-type.js | 7 ++++++-
5 files changed, 47 insertions(+), 9 deletions(-)
diff --git a/src/compiler/irgen.js b/src/compiler/irgen.js
index 5aae5cb1e1b..e99184995ba 100644
--- a/src/compiler/irgen.js
+++ b/src/compiler/irgen.js
@@ -193,12 +193,20 @@ class ScriptTreeGenerator {
kind: 'tw.lastKeyPressed'
};
}
- }
- if (index === -1) {
+
+ // probably a hat parameter.
return {
- kind: 'constant',
- value: 0
+ kind: 'args.parameter',
+ name: name
};
+
+ // TODO: Removing this will put extra overhead on procedure
+ // arguments when used outside of define blocks.
+ //
+ // return {
+ // kind: 'constant',
+ // value: 0
+ // };
}
return {
kind: 'args.stringNumber',
diff --git a/src/compiler/jsgen.js b/src/compiler/jsgen.js
index 85e6e56963d..f247cc48cab 100644
--- a/src/compiler/jsgen.js
+++ b/src/compiler/jsgen.js
@@ -437,6 +437,9 @@ class JSGenerator {
return new TypedInput(`toBoolean(p${node.index})`, TYPE_BOOLEAN);
case 'args.stringNumber':
return new TypedInput(`p${node.index}`, TYPE_UNKNOWN);
+ // hat parameter
+ case 'args.parameter':
+ return new TypedInput(`thread.getParam("${node.name}")`, TYPE_UNKNOWN);
case 'compat':
// Compatibility layer inputs never use flags.
diff --git a/src/engine/block-utility.js b/src/engine/block-utility.js
index 52384c5eca2..bb873d6da78 100644
--- a/src/engine/block-utility.js
+++ b/src/engine/block-utility.js
@@ -202,15 +202,16 @@ class BlockUtility {
* @param {!string} requestedHat Opcode of hats to start.
* @param {object=} optMatchFields Optionally, fields to match on the hat.
* @param {Target=} optTarget Optionally, a target to restrict to.
+ * @param {Target=} optParams Optionally, parameters to push onto the hat.
* @return {Array.} List of threads started by this function.
*/
- startHats (requestedHat, optMatchFields, optTarget) {
+ startHats (requestedHat, optMatchFields, optTarget, optParams) {
// Store thread and sequencer to ensure we can return to the calling block's context.
// startHats may execute further blocks and dirty the BlockUtility's execution context
// and confuse the calling block when we return to it.
const callerThread = this.thread;
const callerSequencer = this.sequencer;
- const result = this.sequencer.runtime.startHats(requestedHat, optMatchFields, optTarget);
+ const result = this.sequencer.runtime.startHats(requestedHat, optMatchFields, optTarget, optParams);
// Restore thread and sequencer to prior values before we return to the calling block.
this.thread = callerThread;
diff --git a/src/engine/runtime.js b/src/engine/runtime.js
index 36b423ff092..3e2fe94d32e 100644
--- a/src/engine/runtime.js
+++ b/src/engine/runtime.js
@@ -133,6 +133,12 @@ const ArgumentTypeMap = (() => {
fieldType: 'field_label_serializable',
fieldName: 'LABEL'
};
+ map[ArgumentType.PARAMETER] = {
+ shadow: {
+ type: 'argument_reporter_string_number',
+ fieldName: 'VALUE'
+ }
+ };
return map;
})();
@@ -1680,6 +1686,11 @@ class Runtime extends EventEmitter {
name: placeholder
};
+ // TO DO: make it impossible to connect anything in here.
+ if (argInfo.type === ArgumentType.PARAMETER) {
+ argJSON.check = null;
+ }
+
const defaultValue =
typeof argInfo.defaultValue === 'undefined' ? null :
maybeFormatMessage(argInfo.defaultValue, this.makeMessageContextForTarget()).toString();
@@ -2241,10 +2252,10 @@ class Runtime extends EventEmitter {
* @param {!string} requestedHatOpcode Opcode of hats to start.
* @param {object=} optMatchFields Optionally, fields to match on the hat.
* @param {Target=} optTarget Optionally, a target to restrict to.
+ * @param {Target=} optParams Optionally, parameters to push onto the hat.
* @return {Array.} List of threads started by this function.
*/
- startHats (requestedHatOpcode,
- optMatchFields, optTarget) {
+ startHats (requestedHatOpcode, optMatchFields, optTarget, optParams) {
if (!Object.prototype.hasOwnProperty.call(this._hats, requestedHatOpcode)) {
// No known hat with this opcode.
return;
@@ -2307,6 +2318,16 @@ class Runtime extends EventEmitter {
// Start the thread with this top block.
newThreads.push(this._pushThread(topBlockId, target));
}, optTarget);
+
+ // If there are "hat parameters", push them
+ if (optParams) {
+ newThreads.forEach(thread => {
+ for (const param in optParams) {
+ thread.pushParam(param, optParams.param);
+ }
+ });
+ }
+
// For compatibility with Scratch 2, edge triggered hats need to be processed before
// threads are stepped. See ScratchRuntime.as for original implementation
newThreads.forEach(thread => {
diff --git a/src/extension-support/argument-type.js b/src/extension-support/argument-type.js
index f0f9ecbe279..6936a06a8dc 100644
--- a/src/extension-support/argument-type.js
+++ b/src/extension-support/argument-type.js
@@ -61,7 +61,12 @@ const ArgumentType = {
/**
* A label text that can be dynamically changed
*/
- LABEL: 'label'
+ LABEL: 'label',
+
+ /**
+ * A reporter that can be defined using startHats.
+ */
+ PARAMETER: 'parameter'
};
module.exports = ArgumentType;
From c478b46846560dfbcc9c1bd0807b2c8580a47134 Mon Sep 17 00:00:00 2001
From: LilyMakesThings <127533508+LilyMakesThings@users.noreply.github.com>
Date: Thu, 22 Feb 2024 19:04:38 +0000
Subject: [PATCH 069/202] Fix hat parameters (#25)
* Fix hat parameters
* missing semi
---
src/compiler/irgen.js | 9 ---------
src/compiler/jsgen.js | 3 +--
src/engine/runtime.js | 18 +++++++++---------
3 files changed, 10 insertions(+), 20 deletions(-)
diff --git a/src/compiler/irgen.js b/src/compiler/irgen.js
index e99184995ba..fa05c6a2578 100644
--- a/src/compiler/irgen.js
+++ b/src/compiler/irgen.js
@@ -194,19 +194,10 @@ class ScriptTreeGenerator {
};
}
- // probably a hat parameter.
return {
kind: 'args.parameter',
name: name
};
-
- // TODO: Removing this will put extra overhead on procedure
- // arguments when used outside of define blocks.
- //
- // return {
- // kind: 'constant',
- // value: 0
- // };
}
return {
kind: 'args.stringNumber',
diff --git a/src/compiler/jsgen.js b/src/compiler/jsgen.js
index f247cc48cab..1a38b47980f 100644
--- a/src/compiler/jsgen.js
+++ b/src/compiler/jsgen.js
@@ -437,9 +437,8 @@ class JSGenerator {
return new TypedInput(`toBoolean(p${node.index})`, TYPE_BOOLEAN);
case 'args.stringNumber':
return new TypedInput(`p${node.index}`, TYPE_UNKNOWN);
- // hat parameter
case 'args.parameter':
- return new TypedInput(`thread.getParam("${node.name}")`, TYPE_UNKNOWN);
+ return new TypedInput(`(thread.getParam("${node.name}") ?? 0)`, TYPE_UNKNOWN);
case 'compat':
// Compatibility layer inputs never use flags.
diff --git a/src/engine/runtime.js b/src/engine/runtime.js
index 3e2fe94d32e..f0acc414b8e 100644
--- a/src/engine/runtime.js
+++ b/src/engine/runtime.js
@@ -2319,15 +2319,6 @@ class Runtime extends EventEmitter {
newThreads.push(this._pushThread(topBlockId, target));
}, optTarget);
- // If there are "hat parameters", push them
- if (optParams) {
- newThreads.forEach(thread => {
- for (const param in optParams) {
- thread.pushParam(param, optParams.param);
- }
- });
- }
-
// For compatibility with Scratch 2, edge triggered hats need to be processed before
// threads are stepped. See ScratchRuntime.as for original implementation
newThreads.forEach(thread => {
@@ -2342,8 +2333,17 @@ class Runtime extends EventEmitter {
} else {
execute(this.sequencer, thread);
thread.goToNextBlock();
+
+ // If there are "hat parameters", push them
+ if (optParams) {
+ thread.initParams();
+ for (const param in optParams) {
+ thread.pushParam(param, optParams[param]);
+ }
+ }
}
});
+
return newThreads;
}
From 8fb72558be3f61f3f8704c542cd8ae36fddceccd Mon Sep 17 00:00:00 2001
From: LilyMakesThings <127533508+LilyMakesThings@users.noreply.github.com>
Date: Thu, 22 Feb 2024 19:08:06 +0000
Subject: [PATCH 070/202] push hat parameters after threads are set up
---
src/engine/runtime.js | 18 ++++++++++--------
1 file changed, 10 insertions(+), 8 deletions(-)
diff --git a/src/engine/runtime.js b/src/engine/runtime.js
index f0acc414b8e..9e6729719f1 100644
--- a/src/engine/runtime.js
+++ b/src/engine/runtime.js
@@ -2333,17 +2333,19 @@ class Runtime extends EventEmitter {
} else {
execute(this.sequencer, thread);
thread.goToNextBlock();
-
- // If there are "hat parameters", push them
- if (optParams) {
- thread.initParams();
- for (const param in optParams) {
- thread.pushParam(param, optParams[param]);
- }
- }
}
});
+ // If there are "hat parameters", push them
+ if (optParams) {
+ newThreads.forEach(thread => {
+ thread.initParams();
+ for (const param in optParams) {
+ thread.pushParam(param, optParams[param]);
+ }
+ });
+ }
+
return newThreads;
}
From 4f458eb60468d33ec9a531f0879dbfde90730980 Mon Sep 17 00:00:00 2001
From: LilyMakesThings <127533508+LilyMakesThings@users.noreply.github.com>
Date: Fri, 23 Feb 2024 14:51:48 +0000
Subject: [PATCH 071/202] Handle new dropdown options (#27)
* Add looking rotation style
* Add milliseconds to current
---
src/blocks/scratch3_sensing.js | 1 +
src/sprites/rendered-target.js | 13 +++++++++++++
2 files changed, 14 insertions(+)
diff --git a/src/blocks/scratch3_sensing.js b/src/blocks/scratch3_sensing.js
index 15c071cb7f5..d5eba4a1792 100644
--- a/src/blocks/scratch3_sensing.js
+++ b/src/blocks/scratch3_sensing.js
@@ -253,6 +253,7 @@ class Scratch3SensingBlocks {
case 'hour': return date.getHours();
case 'minute': return date.getMinutes();
case 'second': return date.getSeconds();
+ case 'millisecond': return date.getMilliseconds();
}
return 0;
}
diff --git a/src/sprites/rendered-target.js b/src/sprites/rendered-target.js
index eab59ca75bf..73ab977358d 100644
--- a/src/sprites/rendered-target.js
+++ b/src/sprites/rendered-target.js
@@ -228,6 +228,14 @@ class RenderedTarget extends Target {
return 'left-right';
}
+ /**
+ * Rotation style for "looking."
+ * @type {string}
+ */
+ static get ROTATION_STYLE_LOOKING () {
+ return "looking";
+ }
+
/**
* Rotation style for "no rotation."
* @type {string}
@@ -303,6 +311,9 @@ class RenderedTarget extends Target {
finalDirection = 90;
const scaleFlip = (this.direction < 0) ? -1 : 1;
finalScale = [scaleFlip * this.size, this.size];
+ } else if (this.rotationStyle === RenderedTarget.ROTATION_LOOKING) {
+ const scaleFlip = (this.direction < 0) ? -1 : 1;
+ finalScale = [scaleFlip * this.size, this.size];
}
return {direction: finalDirection, scale: finalScale};
}
@@ -600,6 +611,8 @@ class RenderedTarget extends Target {
this.rotationStyle = RenderedTarget.ROTATION_STYLE_ALL_AROUND;
} else if (rotationStyle === RenderedTarget.ROTATION_STYLE_LEFT_RIGHT) {
this.rotationStyle = RenderedTarget.ROTATION_STYLE_LEFT_RIGHT;
+ } else if (rotationStyle === RenderedTarget.ROTATION_STYLE_LOOKING) {
+ this.rotationStyle = RenderedTarget.ROTATION_STYLE_LOOKING;
}
if (this.renderer) {
const {direction, scale} = this._getRenderedDirectionAndScale();
From a63276a8c44fbfa63db6345996371e5fbed66e3c Mon Sep 17 00:00:00 2001
From: LilyMakesThings <127533508+LilyMakesThings@users.noreply.github.com>
Date: Fri, 23 Feb 2024 17:52:25 +0000
Subject: [PATCH 072/202] Fix new rotation style
I swear I did this, but I must've only done it on my local server. Darn.
---
src/sprites/rendered-target.js | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/sprites/rendered-target.js b/src/sprites/rendered-target.js
index 73ab977358d..6aaccc249f3 100644
--- a/src/sprites/rendered-target.js
+++ b/src/sprites/rendered-target.js
@@ -311,9 +311,9 @@ class RenderedTarget extends Target {
finalDirection = 90;
const scaleFlip = (this.direction < 0) ? -1 : 1;
finalScale = [scaleFlip * this.size, this.size];
- } else if (this.rotationStyle === RenderedTarget.ROTATION_LOOKING) {
+ } else if (this.rotationStyle === RenderedTarget.ROTATION_STYLE_LOOKING) {
const scaleFlip = (this.direction < 0) ? -1 : 1;
- finalScale = [scaleFlip * this.size, this.size];
+ finalScale = [this.size, scaleFlip * this.size];
}
return {direction: finalDirection, scale: finalScale};
}
From 8fe383527ee9f37f3ee87de975ecbf5f89982dd2 Mon Sep 17 00:00:00 2001
From: LilyMakesThings <127533508+LilyMakesThings@users.noreply.github.com>
Date: Sat, 24 Feb 2024 03:34:11 +0000
Subject: [PATCH 073/202] Support all the new operator/string blocks (#28)
---
src/blocks/scratch3_operators.js | 79 +++++++-------
src/blocks/scratch3_string.js | 181 +++++++++++++++++++++++++++++++
src/compiler/compat-blocks.js | 20 +++-
3 files changed, 242 insertions(+), 38 deletions(-)
create mode 100644 src/blocks/scratch3_string.js
diff --git a/src/blocks/scratch3_operators.js b/src/blocks/scratch3_operators.js
index 9c67754cfc7..f710efb0355 100644
--- a/src/blocks/scratch3_operators.js
+++ b/src/blocks/scratch3_operators.js
@@ -20,20 +20,21 @@ class Scratch3OperatorsBlocks {
operator_subtract: this.subtract,
operator_multiply: this.multiply,
operator_divide: this.divide,
+ operator_exponent: this.exponent,
+ operator_clamp: this.clamp,
operator_lt: this.lt,
+ operator_lt_equals: this.ltEquals,
operator_equals: this.equals,
operator_gt: this.gt,
+ operator_gt_equals: this.gtEquals,
operator_and: this.and,
operator_or: this.or,
operator_xor: this.xor,
operator_not: this.not,
operator_random: this.random,
- operator_join: this.join,
- operator_letter_of: this.letterOf,
- operator_letters_of: this.lettersOf,
- operator_length: this.length,
- operator_contains: this.contains,
operator_mod: this.mod,
+ operator_min: this.min,
+ operator_max: this.max,
operator_round: this.round,
operator_mathop: this.mathop
};
@@ -55,10 +56,18 @@ class Scratch3OperatorsBlocks {
return Cast.toNumber(args.NUM1) / Cast.toNumber(args.NUM2);
}
+ exponent (args) {
+ return Cast.toNumber(args.NUM1) ** Cast.toNumber(args.NUM2);
+ }
+
lt (args) {
return Cast.compare(args.OPERAND1, args.OPERAND2) < 0;
}
+ ltEquals (args) {
+ return Cast.compare(args.OPERAND1, args.OPERAND2) <= 0;
+ }
+
equals (args) {
return Cast.compare(args.OPERAND1, args.OPERAND2) === 0;
}
@@ -67,6 +76,10 @@ class Scratch3OperatorsBlocks {
return Cast.compare(args.OPERAND1, args.OPERAND2) > 0;
}
+ gtEquals (args) {
+ return Cast.compare(args.OPERAND1, args.OPERAND2) >= 0;
+ }
+
and (args) {
return Cast.toBoolean(args.OPERAND1) && Cast.toBoolean(args.OPERAND2);
}
@@ -83,6 +96,30 @@ class Scratch3OperatorsBlocks {
return !Cast.toBoolean(args.OPERAND);
}
+ min (args) {
+ const n1 = Cast.toNumber(args.NUM1);
+ const n2 = Cast.toNumber(args.NUM2);
+ return Math.min(n1, n2);
+ }
+
+ max (args) {
+ const n1 = Cast.toNumber(args.NUM1);
+ const n2 = Cast.toNumber(args.NUM2);
+ return Math.max(n1, n2);
+ }
+
+ clamp (args) {
+ const n = Cast.toNumber(args.NUM);
+ const from = Cast.toNumber(args.FROM);
+ const to = Cast.toNumber(args.TO);
+
+ if (from > to) {
+ return Math.min(Math.max(n, to), from);
+ } else {
+ return Math.min(Math.max(n, from), to);
+ }
+ }
+
random (args) {
return this._random(args.FROM, args.TO);
}
@@ -99,38 +136,6 @@ class Scratch3OperatorsBlocks {
return (Math.random() * (high - low)) + low;
}
- join (args) {
- return Cast.toString(args.STRING1) + Cast.toString(args.STRING2);
- }
-
- letterOf (args) {
- const index = Cast.toNumber(args.LETTER) - 1;
- const str = Cast.toString(args.STRING);
- // Out of bounds?
- if (index < 0 || index >= str.length) {
- return '';
- }
- return str.charAt(index);
- }
-
- lettersOf (args) {
- const index1 = Cast.toNumber(args.LETTER1);
- const index2 = Cast.toNumber(args.LETTER2);
- const str = Cast.toString(args.STRING);
- return str.slice(Math.max(index1, 1) - 1, Math.min(str.length, index2));
- }
-
- length (args) {
- return Cast.toString(args.STRING).length;
- }
-
- contains (args) {
- const format = function (string) {
- return Cast.toString(string).toLowerCase();
- };
- return format(args.STRING1).includes(format(args.STRING2));
- }
-
mod (args) {
const n = Cast.toNumber(args.NUM1);
const modulus = Cast.toNumber(args.NUM2);
diff --git a/src/blocks/scratch3_string.js b/src/blocks/scratch3_string.js
new file mode 100644
index 00000000000..9f3aba87665
--- /dev/null
+++ b/src/blocks/scratch3_string.js
@@ -0,0 +1,181 @@
+const Cast = require('../util/cast.js');
+const MathUtil = require('../util/math-util.js');
+
+class Scratch3OperatorsBlocks {
+ constructor (runtime) {
+ /**
+ * The runtime instantiating this block package.
+ * @type {Runtime}
+ */
+ this.runtime = runtime;
+ }
+
+ /**
+ * Retrieve the block primitives implemented by this package.
+ * @return {object.} Mapping of opcode to Function.
+ */
+ getPrimitives () {
+ return {
+ operator_join: this.join,
+ operator_letter_of: this.letterOf,
+ operator_letters_of: this.lettersOf,
+ operator_length: this.length,
+ operator_contains: this.contains,
+ string_reverse: this.reverse,
+ string_repeat: this.repeat,
+ string_replace: this.replace,
+ string_item_split: this.itemSplit,
+ string_ternary: this.ternary,
+ string_convert: this.convertTo,
+ string_index_of: this.indexOf,
+ string_exactly: this.exactly,
+ string_is: this.stringIs
+ };
+ }
+
+ length (args) {
+ return Cast.toString(args.STRING).length;
+ }
+
+ join (args) {
+ return Cast.toString(args.STRING1) + Cast.toString(args.STRING2);
+ }
+
+ reverse (args) {
+ const str = Cast.toString(args.STRING);
+
+ return str.split("").reverse().join("");
+ }
+
+ repeat (args) {
+ const str = Cast.toString(args.STRING);
+ const times = Cast.toNumber(args.NUMBER);
+
+ return str.repeat(times);
+ }
+
+ replace (args) {
+ const old = Cast.toString(args.REPLACE);
+ const replacer = Cast.toString(args.WITH);
+ const str = Cast.toString(args.STRING);
+
+ return str.replace(new RegExp(old, "gi"), replacer);
+ }
+
+ letterOf (args) {
+ const str = Cast.toString(args.STRING);
+
+ if (args.LETTER === "_last_") {
+ args.LETTER = str.length - 1;
+ } else if (args.LETTER === "_random_") {
+ args.LETTER = Math.floor(Math.random()*str.length);
+ } else {
+ args.LETTER = Cast.toNumber(args.LETTER) - 1;
+ }
+
+ const index = args.LETTER;
+ // Out of bounds?
+ if (index < 0 || index >= str.length) {
+ return '';
+ }
+
+ return str.charAt(index);
+ }
+
+ lettersOf (args) {
+ const index1 = Cast.toNumber(args.LETTER1);
+ const index2 = Cast.toNumber(args.LETTER2);
+ const str = Cast.toString(args.STRING);
+
+ return str.slice(Math.max(index1, 1) - 1, Math.min(str.length, index2));
+ }
+
+ itemSplit (args) {
+ const str = Cast.toString(args.STRING).toLowerCase();
+ const split = Cast.toString(args.SPLIT).toLowerCase();
+
+ if (args.INDEX === "_last_") {
+ args.INDEX = str.length - 1;
+ } else if (args.INDEX === "_random_") {
+ args.INDEX = Math.floor(Math.random()*str.length);
+ } else {
+ args.INDEX = Cast.toNumber(args.INDEX) - 1;
+ }
+
+ const index = args.INDEX;
+ return str.split(split)[index] ?? 0;
+ }
+
+ ternary (args) {
+ const condition = Cast.toBoolean(args.CONDITION);
+ const str1 = Cast.toString(args.STRING1);
+ const str2 = Cast.toString(args.STRING2);
+
+ return condition ? str1 : str2;
+ }
+
+ convertTo (args) {
+ const str = Cast.toString(args.STRING);
+ const convert = Cast.toString(args.CONVERT).toLowerCase();
+
+ if (convert === "lowercase") {
+ return str.toLowerCase();
+ } else {
+ return str.toUpperCase();
+ }
+ }
+
+ indexOf (args) {
+ const find = Cast.toString(args.STRING1).toLowerCase();
+ const str = Cast.toString(args.STRING2).toLowerCase();
+
+ if (args.INDEX === "_last_") {
+ args.INDEX = str.length - 1;
+ } else if (args.INDEX === "_random_") {
+ args.INDEX = Math.floor(Math.random()*str.length);
+ } else {
+ args.INDEX = Cast.toNumber(args.INDEX) - 1;
+ }
+
+ const index = args.INDEX;
+
+ const length = find.length() - 1;
+ if (length > str) return 0;
+
+ let occurences = [];
+ for (let i = 0; i > str.length(); i++) {
+ if (str.substring(i, i + length) === find) {
+ occurences.push(i);
+ }
+ }
+
+ return occurences[index] ?? 0;
+ }
+
+ contains (args) {
+ const format = function (string) {
+ return Cast.toString(string).toLowerCase();
+ };
+ return format(args.STRING1).includes(format(args.STRING2));
+ }
+
+ exactly (args) {
+ const str1 = args.STRING1;
+ const str2 = args.STRING2;
+ return str1 === str2;
+ }
+
+ stringIs (args) { // usb
+ const str = Cast.toString(args.STRING);
+ const check = Cast.toString(args.CONVERT).toLowerCase();
+
+ if (check === "lowercase") {
+ return str.toLowerCase() === str;
+ } else {
+ return str.toUpperCase() === str;
+ }
+ }
+
+}
+
+module.exports = Scratch3OperatorsBlocks;
diff --git a/src/compiler/compat-blocks.js b/src/compiler/compat-blocks.js
index 26c6ec44424..1fcf5675728 100644
--- a/src/compiler/compat-blocks.js
+++ b/src/compiler/compat-blocks.js
@@ -40,7 +40,7 @@ const stacked = [
'sound_playuntildone',
'sound_seteffectto',
'sound_setvolumeto',
- 'sound_stopallsounds'
+ 'sound_stopallsounds',
];
const inputs = [
@@ -51,6 +51,24 @@ const inputs = [
'sensing_loudness',
'sensing_userid',
'sound_volume'
+
+ // USB TO DO: MUST SUPPORT COMPILER
+ 'operator_letter_of',
+ 'operator_clamp',
+ 'operator_exponent',
+ 'operator_gt_equals',
+ 'operator_lt_equals',
+ 'operator_min',
+ 'operator_max',
+ 'string_convert',
+ 'string_exactly',
+ 'string_index_of',
+ 'string_is',
+ 'string_item_split',
+ 'string_repeat',
+ 'string_replace',
+ 'string_reverse',
+ 'string_ternary',
];
module.exports = {
From 085ea5367fccb4d9403e9eb3b9ba2d92602e4feb Mon Sep 17 00:00:00 2001
From: LilyMakesThings <127533508+LilyMakesThings@users.noreply.github.com>
Date: Sat, 24 Feb 2024 03:39:52 +0000
Subject: [PATCH 074/202] Typo
---
src/compiler/compat-blocks.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/compiler/compat-blocks.js b/src/compiler/compat-blocks.js
index 1fcf5675728..ca23580431b 100644
--- a/src/compiler/compat-blocks.js
+++ b/src/compiler/compat-blocks.js
@@ -50,7 +50,7 @@ const inputs = [
'sensing_loud',
'sensing_loudness',
'sensing_userid',
- 'sound_volume'
+ 'sound_volume',
// USB TO DO: MUST SUPPORT COMPILER
'operator_letter_of',
From a8a317df4188ea54762d732d6a60b3a24ec0845c Mon Sep 17 00:00:00 2001
From: LilyMakesThings <127533508+LilyMakesThings@users.noreply.github.com>
Date: Sat, 24 Feb 2024 07:31:40 +0000
Subject: [PATCH 075/202] Support new blocks in compiler (#29)
---
src/blocks/scratch3_string.js | 90 ++++++++++++++------------
src/compiler/compat-blocks.js | 18 ------
src/compiler/irgen.js | 115 +++++++++++++++++++++++++++++++---
src/compiler/jsgen.js | 43 +++++++++++++
src/engine/runtime.js | 1 +
5 files changed, 200 insertions(+), 67 deletions(-)
diff --git a/src/blocks/scratch3_string.js b/src/blocks/scratch3_string.js
index 9f3aba87665..674cbc6c136 100644
--- a/src/blocks/scratch3_string.js
+++ b/src/blocks/scratch3_string.js
@@ -1,7 +1,7 @@
const Cast = require('../util/cast.js');
const MathUtil = require('../util/math-util.js');
-class Scratch3OperatorsBlocks {
+class Scratch3StringBlocks {
constructor (runtime) {
/**
* The runtime instantiating this block package.
@@ -41,20 +41,20 @@ class Scratch3OperatorsBlocks {
return Cast.toString(args.STRING1) + Cast.toString(args.STRING2);
}
- reverse (args) {
+ reverse (args) { // usb
const str = Cast.toString(args.STRING);
return str.split("").reverse().join("");
}
- repeat (args) {
+ repeat (args) { // usb
const str = Cast.toString(args.STRING);
const times = Cast.toNumber(args.NUMBER);
return str.repeat(times);
}
- replace (args) {
+ replace (args) { // usb
const old = Cast.toString(args.REPLACE);
const replacer = Cast.toString(args.WITH);
const str = Cast.toString(args.STRING);
@@ -64,25 +64,28 @@ class Scratch3OperatorsBlocks {
letterOf (args) {
const str = Cast.toString(args.STRING);
+ return this._getLetterOf(str, args.LETTER);
+ }
- if (args.LETTER === "_last_") {
- args.LETTER = str.length - 1;
- } else if (args.LETTER === "_random_") {
- args.LETTER = Math.floor(Math.random()*str.length);
+ _getLetterOf (string, index) { // usb // used by compiler
+ // usb: we support some weird dropdowns now
+ if (index === "_last_") {
+ index = string.length - 1;
+ } else if (index === "_random_") {
+ index = Math.floor(Math.random()*string.length);
} else {
- args.LETTER = Cast.toNumber(args.LETTER) - 1;
+ index = Cast.toNumber(index) - 1;
}
- const index = args.LETTER;
// Out of bounds?
- if (index < 0 || index >= str.length) {
+ if (index < 0 || index >= string.length) {
return '';
}
- return str.charAt(index);
+ return string.charAt(index);
}
- lettersOf (args) {
+ lettersOf (args) { // usb
const index1 = Cast.toNumber(args.LETTER1);
const index2 = Cast.toNumber(args.LETTER2);
const str = Cast.toString(args.STRING);
@@ -90,23 +93,26 @@ class Scratch3OperatorsBlocks {
return str.slice(Math.max(index1, 1) - 1, Math.min(str.length, index2));
}
- itemSplit (args) {
+ itemSplit (args) { // usb
const str = Cast.toString(args.STRING).toLowerCase();
const split = Cast.toString(args.SPLIT).toLowerCase();
- if (args.INDEX === "_last_") {
- args.INDEX = str.length - 1;
- } else if (args.INDEX === "_random_") {
- args.INDEX = Math.floor(Math.random()*str.length);
+ return this._getIndexFromSplit(str, split, args.INDEX);
+ }
+
+ _getIndexFromSplit (string, split, index) { // used by compiler
+ if (index === "_last_") {
+ index = string.length - 1;
+ } else if (index === "_random_") {
+ index = Math.floor(Math.random()*string.length);
} else {
- args.INDEX = Cast.toNumber(args.INDEX) - 1;
+ index = Cast.toNumber(index) - 1;
}
- const index = args.INDEX;
- return str.split(split)[index] ?? 0;
+ return string.split(split)[index] ?? 0;
}
- ternary (args) {
+ ternary (args) { // usb
const condition = Cast.toBoolean(args.CONDITION);
const str1 = Cast.toString(args.STRING1);
const str2 = Cast.toString(args.STRING2);
@@ -114,37 +120,43 @@ class Scratch3OperatorsBlocks {
return condition ? str1 : str2;
}
- convertTo (args) {
+ convertTo (args) { // usb
const str = Cast.toString(args.STRING);
const convert = Cast.toString(args.CONVERT).toLowerCase();
- if (convert === "lowercase") {
- return str.toLowerCase();
+ return this._convertString(str, convert);
+ }
+
+ _convertString (string, textCase) { // used by compiler
+ if (textCase === "lowercase") {
+ return string.toLowerCase();
} else {
- return str.toUpperCase();
+ return string.toUpperCase();
}
}
- indexOf (args) {
+ indexOf (args) { // usb
const find = Cast.toString(args.STRING1).toLowerCase();
const str = Cast.toString(args.STRING2).toLowerCase();
- if (args.INDEX === "_last_") {
- args.INDEX = str.length - 1;
- } else if (args.INDEX === "_random_") {
- args.INDEX = Math.floor(Math.random()*str.length);
+ return this._getNumberIndex(find, str, args.INDEX);
+ }
+
+ _getNumberIndex (find, string, index) { // used by compiler
+ if (index === "_last_") {
+ index = string.length - 1;
+ } else if (index === "_random_") {
+ index = Math.floor(Math.random()*string.length);
} else {
- args.INDEX = Cast.toNumber(args.INDEX) - 1;
+ index = Cast.toNumber(index) - 1;
}
- const index = args.INDEX;
-
const length = find.length() - 1;
- if (length > str) return 0;
+ if (length > string) return 0;
let occurences = [];
- for (let i = 0; i > str.length(); i++) {
- if (str.substring(i, i + length) === find) {
+ for (let i = 0; i > string.length(); i++) {
+ if (string.substring(i, i + length) === find) {
occurences.push(i);
}
}
@@ -159,7 +171,7 @@ class Scratch3OperatorsBlocks {
return format(args.STRING1).includes(format(args.STRING2));
}
- exactly (args) {
+ exactly (args) { // usb
const str1 = args.STRING1;
const str2 = args.STRING2;
return str1 === str2;
@@ -178,4 +190,4 @@ class Scratch3OperatorsBlocks {
}
-module.exports = Scratch3OperatorsBlocks;
+module.exports = Scratch3StringBlocks;
diff --git a/src/compiler/compat-blocks.js b/src/compiler/compat-blocks.js
index ca23580431b..b3766cc8262 100644
--- a/src/compiler/compat-blocks.js
+++ b/src/compiler/compat-blocks.js
@@ -51,24 +51,6 @@ const inputs = [
'sensing_loudness',
'sensing_userid',
'sound_volume',
-
- // USB TO DO: MUST SUPPORT COMPILER
- 'operator_letter_of',
- 'operator_clamp',
- 'operator_exponent',
- 'operator_gt_equals',
- 'operator_lt_equals',
- 'operator_min',
- 'operator_max',
- 'string_convert',
- 'string_exactly',
- 'string_index_of',
- 'string_is',
- 'string_item_split',
- 'string_repeat',
- 'string_replace',
- 'string_reverse',
- 'string_ternary',
];
module.exports = {
diff --git a/src/compiler/irgen.js b/src/compiler/irgen.js
index fa05c6a2578..ab3bc193506 100644
--- a/src/compiler/irgen.js
+++ b/src/compiler/irgen.js
@@ -226,6 +226,15 @@ class ScriptTreeGenerator {
};
}
+ case 'camera_xposition':
+ return {
+ kind: 'camera.x'
+ };
+ case 'camera_yposition':
+ return {
+ kind: 'camera.y'
+ };
+
case 'control_get_counter':
return {
kind: 'counter.get'
@@ -328,6 +337,13 @@ class ScriptTreeGenerator {
left: this.descendInputOfBlock(block, 'OPERAND1'),
right: this.descendInputOfBlock(block, 'OPERAND2')
};
+ case 'operator_clamp':
+ return {
+ kind: 'op.clamp',
+ num: this.descendInputOfBlock(block, 'NUM'),
+ left: this.descendInputOfBlock(block, 'FROM'),
+ right: this.descendInputOfBlock(block, 'TO')
+ };
case 'operator_contains':
return {
kind: 'op.contains',
@@ -340,6 +356,12 @@ class ScriptTreeGenerator {
left: this.descendInputOfBlock(block, 'NUM1'),
right: this.descendInputOfBlock(block, 'NUM2')
};
+ case 'operator_exponent':
+ return {
+ kind: 'op.exponent',
+ left: this.descendInputOfBlock(block, 'NUM1'),
+ right: this.descendInputOfBlock(block, 'NUM2')
+ };
case 'operator_equals':
return {
kind: 'op.equals',
@@ -352,6 +374,12 @@ class ScriptTreeGenerator {
left: this.descendInputOfBlock(block, 'OPERAND1'),
right: this.descendInputOfBlock(block, 'OPERAND2')
};
+ case 'operator_gt_equals':
+ return {
+ kind: 'op.greaterEqual',
+ left: this.descendInputOfBlock(block, 'OPERAND1'),
+ right: this.descendInputOfBlock(block, 'OPERAND2')
+ };
case 'operator_join':
return {
kind: 'op.join',
@@ -382,6 +410,12 @@ class ScriptTreeGenerator {
left: this.descendInputOfBlock(block, 'OPERAND1'),
right: this.descendInputOfBlock(block, 'OPERAND2')
};
+ case 'operator_lt_equals':
+ return {
+ kind: 'op.lessEqual',
+ left: this.descendInputOfBlock(block, 'OPERAND1'),
+ right: this.descendInputOfBlock(block, 'OPERAND2')
+ };
case 'operator_mathop': {
const value = this.descendInputOfBlock(block, 'NUM');
const operator = block.fields.OPERATOR.value.toLowerCase();
@@ -454,6 +488,18 @@ class ScriptTreeGenerator {
left: this.descendInputOfBlock(block, 'NUM1'),
right: this.descendInputOfBlock(block, 'NUM2')
};
+ case 'operator_min':
+ return {
+ kind: 'op.min',
+ left: this.descendInputOfBlock(block, 'NUM1'),
+ right: this.descendInputOfBlock(block, 'NUM2')
+ };
+ case 'operator_max':
+ return {
+ kind: 'op.max',
+ left: this.descendInputOfBlock(block, 'NUM1'),
+ right: this.descendInputOfBlock(block, 'NUM2')
+ };
case 'operator_multiply':
return {
kind: 'op.multiply',
@@ -471,12 +517,6 @@ class ScriptTreeGenerator {
left: this.descendInputOfBlock(block, 'OPERAND1'),
right: this.descendInputOfBlock(block, 'OPERAND2')
};
- case 'operator_xor':
- return {
- kind: 'op.xor',
- left: this.descendInputOfBlock(block, 'OPERAND1'),
- right: this.descendInputOfBlock(block, 'OPERAND2')
- };
case 'operator_random': {
const from = this.descendInputOfBlock(block, 'FROM');
const to = this.descendInputOfBlock(block, 'TO');
@@ -555,6 +595,12 @@ class ScriptTreeGenerator {
left: this.descendInputOfBlock(block, 'NUM1'),
right: this.descendInputOfBlock(block, 'NUM2')
};
+ case 'operator_xor':
+ return {
+ kind: 'op.xor',
+ left: this.descendInputOfBlock(block, 'OPERAND1'),
+ right: this.descendInputOfBlock(block, 'OPERAND2')
+ };
case 'procedures_call':
return this.descendProcedure(block);
@@ -656,13 +702,62 @@ class ScriptTreeGenerator {
kind: 'sensing.username'
};
- case 'camera_xposition':
+ case 'string_convert':
return {
- kind: 'camera.x'
+ kind: 'str.convert',
+ left: this.descendInputOfBlock(block, 'STRING'),
+ right: this.descendInputOfBlock(block, 'CONVERT'),
};
- case 'camera_yposition':
+ case 'string_exactly':
return {
- kind: 'camera.y'
+ kind: 'str.exactly',
+ left: this.descendInputOfBlock(block, 'STRING1'),
+ right: this.descendInputOfBlock(block, 'STRING2')
+ };
+ case 'string_index_of':
+ return {
+ kind: 'str.index',
+ num: this.descendInputOfBlock(block, 'INDEX'),
+ left: this.descendInputOfBlock(block, 'STRING1'),
+ right: this.descendInputOfBlock(block, 'STRING2')
+ };
+ case 'string_is':
+ return {
+ kind: 'str.is',
+ left: this.descendInputOfBlock(block, 'STRING'),
+ right: this.descendInputOfBlock(block, 'CONVERT')
+ };
+ case 'string_item_split':
+ return {
+ kind: 'str.split',
+ num: this.descendInputOfBlock(block, 'INDEX'),
+ str: this.descendInputOfBlock(block, 'STRING'),
+ split: this.descendInputOfBlock(block, 'SPLIT')
+ };
+ case 'string_repeat':
+ return {
+ kind: 'str.repeat',
+ str: this.descendInputOfBlock(block, 'STRING'),
+ num: this.descendInputOfBlock(block, 'NUMBER')
+ };
+ case 'string_replace':
+ return {
+ kind: 'str.replace',
+ left: this.descendInputOfBlock(block, 'REPLACE'),
+ right: this.descendInputOfBlock(block, 'WITH'),
+ str: this.descendInputOfBlock(block, 'STRING')
+ };
+ case 'string_reverse':
+ return {
+ kind: 'str.reverse',
+ str: this.descendInputOfBlock(block, 'STRING')
+ };
+ case 'string_ternary':
+ return {
+ kind: 'str.convert',
+ operand: this.descendInputOfBlock(block, 'CONDITION'),
+ left: this.descendInputOfBlock(block, 'STRING1'),
+ right: this.descendInputOfBlock(block, 'STRING2')
};
case 'sound_sounds_menu':
diff --git a/src/compiler/jsgen.js b/src/compiler/jsgen.js
index 1a38b47980f..d817ac99ade 100644
--- a/src/compiler/jsgen.js
+++ b/src/compiler/jsgen.js
@@ -521,6 +521,8 @@ class JSGenerator {
return new TypedInput(`((Math.atan(${this.descendInput(node.value).asNumber()}) * 180) / Math.PI)`, TYPE_NUMBER);
case 'op.ceiling':
return new TypedInput(`Math.ceil(${this.descendInput(node.value).asNumber()})`, TYPE_NUMBER);
+ case 'op.clamp':
+ return new TypedInput(`Math.min(Math.max(${this.descendInput(node.num).asNumber()}, ${this.descendInput(node.left).asNumber()}), ${this.descendInput(node.right).asNumber()})`, TYPE_NUMBER);
case 'op.contains':
return new TypedInput(`(${this.descendInput(node.string).asString()}.toLowerCase().indexOf(${this.descendInput(node.contains).asString()}.toLowerCase()) !== -1)`, TYPE_BOOLEAN);
case 'op.cos':
@@ -551,6 +553,8 @@ class JSGenerator {
// No compile-time optimizations possible - use fallback method.
return new TypedInput(`compareEqual(${left.asUnknown()}, ${right.asUnknown()})`, TYPE_BOOLEAN);
}
+ case 'op.exponent':
+ return new TypedInput(`(${this.descendInput(node.left).asNumber()} ** ${this.descendInput(node.right).asNumber()})`, TYPE_NUMBER);
case 'op.e^':
return new TypedInput(`Math.exp(${this.descendInput(node.value).asNumber()})`, TYPE_NUMBER);
case 'op.floor':
@@ -573,6 +577,11 @@ class JSGenerator {
// No compile-time optimizations possible - use fallback method.
return new TypedInput(`compareGreaterThan(${left.asUnknown()}, ${right.asUnknown()})`, TYPE_BOOLEAN);
}
+ case 'op.greaterEqual': {
+ const left = this.descendInput(node.left);
+ const right = this.descendInput(node.right);
+ return new TypedInput(`(${left.asUnknown()} >= ${right.asUnknown()})`, TYPE_BOOLEAN);
+ }
case 'op.join':
return new TypedInput(`(${this.descendInput(node.left).asString()} + ${this.descendInput(node.right).asString()})`, TYPE_STRING);
case 'op.length':
@@ -595,6 +604,11 @@ class JSGenerator {
// No compile-time optimizations possible - use fallback method.
return new TypedInput(`compareLessThan(${left.asUnknown()}, ${right.asUnknown()})`, TYPE_BOOLEAN);
}
+ case 'op.lessEqual': {
+ const left = this.descendInput(node.left);
+ const right = this.descendInput(node.right);
+ return new TypedInput(`(${left.asUnknown()} <= ${right.asUnknown()})`, TYPE_BOOLEAN);
+ }
case 'op.letterOf':
return new TypedInput(`((${this.descendInput(node.string).asString()})[(${this.descendInput(node.letter).asNumber()} | 0) - 1] || "")`, TYPE_STRING);
case 'op.lettersOf':
@@ -609,6 +623,10 @@ class JSGenerator {
this.descendedIntoModulo = true;
// Needs to be marked as NaN because mod(0, 0) (and others) == NaN
return new TypedInput(`mod(${this.descendInput(node.left).asNumber()}, ${this.descendInput(node.right).asNumber()})`, TYPE_NUMBER_NAN);
+ case 'op.min':
+ return new TypedInput(`Math.min(${this.descendInput(node.left).asNumber()}, ${this.descendInput(node.right).asNumber()})`, TYPE_NUMBER);
+ case 'op.max':
+ return new TypedInput(`Math.max(${this.descendInput(node.left).asNumber()}, ${this.descendInput(node.right).asNumber()})`, TYPE_NUMBER);
case 'op.multiply':
// Needs to be marked as NaN because Infinity * 0 === NaN
return new TypedInput(`(${this.descendInput(node.left).asNumber()} * ${this.descendInput(node.right).asNumber()})`, TYPE_NUMBER_NAN);
@@ -744,6 +762,31 @@ class JSGenerator {
case 'sensing.year':
return new TypedInput(`(new Date().getFullYear())`, TYPE_NUMBER);
+ case 'str.convert':
+ return new TypedInput(`runtime.ext_scratch3_string._convertString(${this.descendInput(node.left).asString()}, ${this.descendInput(node.right).asString()})`, TYPE_STRING);
+ case 'str.exactly':
+ return new TypedInput(`(${this.descendInput(node.left)} === ${this.descendInput(node.right)})`, TYPE_UNKNOWN);
+ case 'str.index':
+ return new TypedInput(`runtime.ext_scratch3_string._getNumberIndex(${this.descendInput(node.left).asString()}, ${this.descendInput(node.right).asString()}, ${this.descendInput(node.num).asNumber()})`, TYPE_NUMBER);
+ case 'str.is': {
+ const str = this.descendInput(node.left).asString();
+ if (this.descendInput(node.right).asString().toLowerCase() === "uppercase") {
+ return new TypedInput(`${str.toUpperCase() === str}`, TYPE_BOOLEAN);
+ } else {
+ return new TypedInput(`${str.toLowerCase() === str}`, TYPE_BOOLEAN);
+ }
+ }
+ case 'str.split':
+ return new TypedInput(`runtime.ext_scratch3_string._getIndexFromSplit(${this.descendInput(node.str).asString()}, ${this.descendInput(node.split).asString()}, ${this.descendInput(node.num).asNumber()})`, TYPE_STRING);
+ case 'str.repeat':
+ return new TypedInput(`(${this.descendInput(node.str).asString()}.repeat(${this.descendInput(node.num).asNumber()}))`, TYPE_STRING);
+ case 'str.replace':
+ return new TypedInput(`${this.descendInput(node.str).asString()}.replace(new RegExp(${this.descendInput(node.left).asString()}, "gi"), ${this.descendInput(node.right).asString()})`, TYPE_STRING);
+ case 'str.reverse':
+ return new TypedInput(`${this.descendInput(node.str).asString()}.split("").reverse().join("");`, TYPE_STRING);
+ case 'str.ternary':
+ return new TypedInput(`(${this.descendInput(node.operand).asString()}) ? ${this.descendInput(node.left).asString()} : ${this.descendInput(node.right).asString()}`, TYPE_UNKNOWN);
+
case 'camera.x':
return new TypedInput('runtime.camera.x', TYPE_NUMBER);
case 'camera.y':
diff --git a/src/engine/runtime.js b/src/engine/runtime.js
index 9e6729719f1..1132f2bae21 100644
--- a/src/engine/runtime.js
+++ b/src/engine/runtime.js
@@ -41,6 +41,7 @@ const defaultBlockPackages = {
scratch3_looks: require('../blocks/scratch3_looks'),
scratch3_motion: require('../blocks/scratch3_motion'),
scratch3_operators: require('../blocks/scratch3_operators'),
+ scratch3_string: require('../blocks/scratch3_string'),
scratch3_sound: require('../blocks/scratch3_sound'),
scratch3_sensing: require('../blocks/scratch3_sensing'),
scratch3_camera: require('../blocks/scratch3_camera'),
From 9678facf8ed23e003850cf7bea19c30c4e85c506 Mon Sep 17 00:00:00 2001
From: LilyMakesThings <127533508+LilyMakesThings@users.noreply.github.com>
Date: Sat, 24 Feb 2024 08:03:53 +0000
Subject: [PATCH 076/202] Various compiler fixes (#30)
* Fix string operations in interpreter
* fix blocks in compiler
* fix convert value fields
---
src/blocks/scratch3_string.js | 18 +++++++++---------
src/compiler/irgen.js | 4 ++--
src/compiler/jsgen.js | 4 ++--
3 files changed, 13 insertions(+), 13 deletions(-)
diff --git a/src/blocks/scratch3_string.js b/src/blocks/scratch3_string.js
index 674cbc6c136..4fc4022c1f9 100644
--- a/src/blocks/scratch3_string.js
+++ b/src/blocks/scratch3_string.js
@@ -69,9 +69,9 @@ class Scratch3StringBlocks {
_getLetterOf (string, index) { // usb // used by compiler
// usb: we support some weird dropdowns now
- if (index === "_last_") {
+ if (index.toLowerCase() === "last") {
index = string.length - 1;
- } else if (index === "_random_") {
+ } else if (index.toLowerCase() === "random") {
index = Math.floor(Math.random()*string.length);
} else {
index = Cast.toNumber(index) - 1;
@@ -101,9 +101,9 @@ class Scratch3StringBlocks {
}
_getIndexFromSplit (string, split, index) { // used by compiler
- if (index === "_last_") {
+ if (index.toLowerCase() === "last") {
index = string.length - 1;
- } else if (index === "_random_") {
+ } else if (index.toLowerCase() === "random") {
index = Math.floor(Math.random()*string.length);
} else {
index = Cast.toNumber(index) - 1;
@@ -142,20 +142,20 @@ class Scratch3StringBlocks {
return this._getNumberIndex(find, str, args.INDEX);
}
- _getNumberIndex (find, string, index) { // used by compiler
- if (index === "_last_") {
+ _getNumberIndex (find, string, index) { // used by compile
+ if (index.toLowerCase() === "last") {
index = string.length - 1;
- } else if (index === "_random_") {
+ } else if (index.toLowerCase() === "random") {
index = Math.floor(Math.random()*string.length);
} else {
index = Cast.toNumber(index) - 1;
}
- const length = find.length() - 1;
+ const length = find.length - 1;
if (length > string) return 0;
let occurences = [];
- for (let i = 0; i > string.length(); i++) {
+ for (let i = 0; i > string.length; i++) {
if (string.substring(i, i + length) === find) {
occurences.push(i);
}
diff --git a/src/compiler/irgen.js b/src/compiler/irgen.js
index ab3bc193506..6932bb9476a 100644
--- a/src/compiler/irgen.js
+++ b/src/compiler/irgen.js
@@ -706,7 +706,7 @@ class ScriptTreeGenerator {
return {
kind: 'str.convert',
left: this.descendInputOfBlock(block, 'STRING'),
- right: this.descendInputOfBlock(block, 'CONVERT'),
+ right: block.fields.CONVERT.value
};
case 'string_exactly':
return {
@@ -725,7 +725,7 @@ class ScriptTreeGenerator {
return {
kind: 'str.is',
left: this.descendInputOfBlock(block, 'STRING'),
- right: this.descendInputOfBlock(block, 'CONVERT')
+ right: block.fields.CONVERT.value
};
case 'string_item_split':
return {
diff --git a/src/compiler/jsgen.js b/src/compiler/jsgen.js
index d817ac99ade..c7a2f058970 100644
--- a/src/compiler/jsgen.js
+++ b/src/compiler/jsgen.js
@@ -765,7 +765,7 @@ class JSGenerator {
case 'str.convert':
return new TypedInput(`runtime.ext_scratch3_string._convertString(${this.descendInput(node.left).asString()}, ${this.descendInput(node.right).asString()})`, TYPE_STRING);
case 'str.exactly':
- return new TypedInput(`(${this.descendInput(node.left)} === ${this.descendInput(node.right)})`, TYPE_UNKNOWN);
+ return new TypedInput(`(${this.descendInput(node.left).asUnknown()} === ${this.descendInput(node.right).asUnknown()})`, TYPE_UNKNOWN);
case 'str.index':
return new TypedInput(`runtime.ext_scratch3_string._getNumberIndex(${this.descendInput(node.left).asString()}, ${this.descendInput(node.right).asString()}, ${this.descendInput(node.num).asNumber()})`, TYPE_NUMBER);
case 'str.is': {
@@ -785,7 +785,7 @@ class JSGenerator {
case 'str.reverse':
return new TypedInput(`${this.descendInput(node.str).asString()}.split("").reverse().join("");`, TYPE_STRING);
case 'str.ternary':
- return new TypedInput(`(${this.descendInput(node.operand).asString()}) ? ${this.descendInput(node.left).asString()} : ${this.descendInput(node.right).asString()}`, TYPE_UNKNOWN);
+ return new TypedInput(`(${this.descendInput(node.operand).asBoolean()}) ? ${this.descendInput(node.left).asUnknown()} : ${this.descendInput(node.right).asUnknown()})`, TYPE_UNKNOWN);
case 'camera.x':
return new TypedInput('runtime.camera.x', TYPE_NUMBER);
From fb6c7b18992d0a3944b3cc9e96603438898c30d4 Mon Sep 17 00:00:00 2001
From: LilyMakesThings <127533508+LilyMakesThings@users.noreply.github.com>
Date: Sat, 24 Feb 2024 20:47:46 +0000
Subject: [PATCH 077/202] More fixes for the compiler (#31)
---
src/blocks/scratch3_string.js | 12 ++++++------
src/compiler/jsgen.js | 6 +++---
2 files changed, 9 insertions(+), 9 deletions(-)
diff --git a/src/blocks/scratch3_string.js b/src/blocks/scratch3_string.js
index 4fc4022c1f9..a8bda69fea0 100644
--- a/src/blocks/scratch3_string.js
+++ b/src/blocks/scratch3_string.js
@@ -69,9 +69,9 @@ class Scratch3StringBlocks {
_getLetterOf (string, index) { // usb // used by compiler
// usb: we support some weird dropdowns now
- if (index.toLowerCase() === "last") {
+ if (index === "last") {
index = string.length - 1;
- } else if (index.toLowerCase() === "random") {
+ } else if (index === "random") {
index = Math.floor(Math.random()*string.length);
} else {
index = Cast.toNumber(index) - 1;
@@ -101,9 +101,9 @@ class Scratch3StringBlocks {
}
_getIndexFromSplit (string, split, index) { // used by compiler
- if (index.toLowerCase() === "last") {
+ if (index === "last") {
index = string.length - 1;
- } else if (index.toLowerCase() === "random") {
+ } else if (index === "random") {
index = Math.floor(Math.random()*string.length);
} else {
index = Cast.toNumber(index) - 1;
@@ -143,9 +143,9 @@ class Scratch3StringBlocks {
}
_getNumberIndex (find, string, index) { // used by compile
- if (index.toLowerCase() === "last") {
+ if (index === "last") {
index = string.length - 1;
- } else if (index.toLowerCase() === "random") {
+ } else if (index === "random") {
index = Math.floor(Math.random()*string.length);
} else {
index = Cast.toNumber(index) - 1;
diff --git a/src/compiler/jsgen.js b/src/compiler/jsgen.js
index c7a2f058970..4c427d7c7e5 100644
--- a/src/compiler/jsgen.js
+++ b/src/compiler/jsgen.js
@@ -763,14 +763,14 @@ class JSGenerator {
return new TypedInput(`(new Date().getFullYear())`, TYPE_NUMBER);
case 'str.convert':
- return new TypedInput(`runtime.ext_scratch3_string._convertString(${this.descendInput(node.left).asString()}, ${this.descendInput(node.right).asString()})`, TYPE_STRING);
+ return new TypedInput(`runtime.ext_scratch3_string._convertString(${this.descendInput(node.left).asString()}, ${node.right})`, TYPE_STRING);
case 'str.exactly':
return new TypedInput(`(${this.descendInput(node.left).asUnknown()} === ${this.descendInput(node.right).asUnknown()})`, TYPE_UNKNOWN);
case 'str.index':
return new TypedInput(`runtime.ext_scratch3_string._getNumberIndex(${this.descendInput(node.left).asString()}, ${this.descendInput(node.right).asString()}, ${this.descendInput(node.num).asNumber()})`, TYPE_NUMBER);
case 'str.is': {
const str = this.descendInput(node.left).asString();
- if (this.descendInput(node.right).asString().toLowerCase() === "uppercase") {
+ if (node.right.toLowerCase() === "uppercase") {
return new TypedInput(`${str.toUpperCase() === str}`, TYPE_BOOLEAN);
} else {
return new TypedInput(`${str.toLowerCase() === str}`, TYPE_BOOLEAN);
@@ -785,7 +785,7 @@ class JSGenerator {
case 'str.reverse':
return new TypedInput(`${this.descendInput(node.str).asString()}.split("").reverse().join("");`, TYPE_STRING);
case 'str.ternary':
- return new TypedInput(`(${this.descendInput(node.operand).asBoolean()}) ? ${this.descendInput(node.left).asUnknown()} : ${this.descendInput(node.right).asUnknown()})`, TYPE_UNKNOWN);
+ return new TypedInput(`(${this.descendInput(node.operand).asBoolean()} ? ${this.descendInput(node.left).asString()} : ${this.descendInput(node.right).asString()})`, TYPE_UNKNOWN);
case 'camera.x':
return new TypedInput('runtime.camera.x', TYPE_NUMBER);
From 6dfb24edf3040dc76cab1ae70beda278c069ccb0 Mon Sep 17 00:00:00 2001
From: LilyMakesThings <127533508+LilyMakesThings@users.noreply.github.com>
Date: Sun, 25 Feb 2024 07:03:22 +0000
Subject: [PATCH 078/202] Incorporate new Object and Array types (#32)
---
src/engine/runtime.js | 14 +++++++++
src/engine/scratch-blocks-constants.js | 12 ++++++--
src/extension-support/argument-type.js | 42 ++++++++++++++++----------
src/extension-support/block-type.js | 22 ++++++++++----
4 files changed, 65 insertions(+), 25 deletions(-)
diff --git a/src/engine/runtime.js b/src/engine/runtime.js
index 1132f2bae21..291d9229508 100644
--- a/src/engine/runtime.js
+++ b/src/engine/runtime.js
@@ -97,6 +97,12 @@ const ArgumentTypeMap = (() => {
map[ArgumentType.BOOLEAN] = {
check: 'Boolean'
};
+ map[ArgumentType.ARRAY] = {
+ check: 'Array'
+ };
+ map[ArgumentType.OBJECT] = {
+ check: 'Object'
+ };
map[ArgumentType.MATRIX] = {
shadow: {
type: 'matrix',
@@ -1458,6 +1464,14 @@ class Runtime extends EventEmitter {
blockJSON.output = blockInfo.allowDropAnywhere ? null : 'String'; // TODO: distinguish number & string here?
blockJSON.outputShape = ScratchBlocksConstants.OUTPUT_SHAPE_SQUARE;
break;
+ case BlockType.ARRAY:
+ blockJSON.output = 'Array';
+ blockJSON.outputShape = ScratchBlocksConstants.OUTPUT_SHAPE_SQUARE;
+ break;
+ case BlockType.OBJECT:
+ blockJSON.output = 'Object';
+ blockJSON.outputShape = ScratchBlocksConstants.OUTPUT_SHAPE_OBJECT;
+ break;
}
const blockText = Array.isArray(blockInfo.text) ? blockInfo.text : [blockInfo.text];
diff --git a/src/engine/scratch-blocks-constants.js b/src/engine/scratch-blocks-constants.js
index ee558549705..5a942d06a55 100644
--- a/src/engine/scratch-blocks-constants.js
+++ b/src/engine/scratch-blocks-constants.js
@@ -12,16 +12,22 @@ const ScratchBlocksConstants = {
OUTPUT_SHAPE_HEXAGONAL: 1,
/**
- * ENUM for output shape: rounded (numbers).
+ * ENUM for output shape: rounded (numbers and strings).
* @const
*/
OUTPUT_SHAPE_ROUND: 2,
/**
- * ENUM for output shape: squared (any/all values; strings).
+ * ENUM for output shape: squared (arrays).
* @const
*/
- OUTPUT_SHAPE_SQUARE: 3
+ OUTPUT_SHAPE_SQUARE: 3,
+
+ /**
+ * ENUM for output shape: object (objects).
+ * @const
+ */
+ OUTPUT_SHAPE_OBJECT: 4
};
module.exports = ScratchBlocksConstants;
diff --git a/src/extension-support/argument-type.js b/src/extension-support/argument-type.js
index 6936a06a8dc..f8cf31a4e3a 100644
--- a/src/extension-support/argument-type.js
+++ b/src/extension-support/argument-type.js
@@ -8,6 +8,11 @@ const ArgumentType = {
*/
ANGLE: 'angle',
+ /**
+ * A representation of a JSON array.
+ */
+ ARRAY: 'array',
+
/**
* Boolean value with hexagonal placeholder
*/
@@ -19,14 +24,19 @@ const ArgumentType = {
COLOR: 'color',
/**
- * Numeric value with text field
+ * Name of costume in the current target
*/
- NUMBER: 'number',
+ COSTUME: 'costume',
/**
- * String value with text field
+ * Inline image on block (as part of the label)
*/
- STRING: 'string',
+ IMAGE: 'image',
+
+ /**
+ * A label text that can be dynamically changed
+ */
+ LABEL: 'label',
/**
* String value with matrix field
@@ -39,34 +49,34 @@ const ArgumentType = {
NOTE: 'note',
/**
- * Inline image on block (as part of the label)
+ * Numeric value with text field
*/
- IMAGE: 'image',
+ NUMBER: 'number',
/**
- * Name of costume in the current target
+ * A representation of a JSON object.
*/
- COSTUME: 'costume',
+ OBJECT: 'Object',
/**
- * Name of sound in the current target
+ * A reporter that can be defined using startHats.
*/
- SOUND: 'sound',
+ PARAMETER: 'parameter',
/**
- * Name of variable in the current specified target(s)
+ * Name of sound in the current target
*/
- VARIABLE: 'variable',
+ SOUND: 'sound',
/**
- * A label text that can be dynamically changed
+ * String value with text field
*/
- LABEL: 'label',
+ STRING: 'string',
/**
- * A reporter that can be defined using startHats.
+ * Name of variable in the current specified target(s)
*/
- PARAMETER: 'parameter'
+ VARIABLE: 'variable',
};
module.exports = ArgumentType;
diff --git a/src/extension-support/block-type.js b/src/extension-support/block-type.js
index 4680ef4af10..8c24b04dc9f 100644
--- a/src/extension-support/block-type.js
+++ b/src/extension-support/block-type.js
@@ -3,6 +3,16 @@
* @enum {string}
*/
const BlockType = {
+ /**
+ * Array reporter with a square shape.
+ */
+ ARRAY: 'array',
+
+ /**
+ * Object reporter with a lemon shape.
+ */
+ OBJECT: 'Object',
+
/**
* Boolean reporter with hexagonal shape
*/
@@ -40,6 +50,12 @@ const BlockType = {
*/
HAT: 'hat',
+ /**
+ * Specialized reporter block that allows for the insertion and evaluation
+ * of a substack.
+ */
+ INLINE: 'inline',
+
/**
* Specialized command block which may or may not run a child branch
* If a child branch runs, the thread evaluates the loop block again.
@@ -55,12 +71,6 @@ const BlockType = {
* Arbitrary scratch-blocks XML.
*/
XML: 'xml',
-
- /**
- * Specialized reporter block that allows for the insertion and evaluation
- * of a substack.
- */
- INLINE: 'inline'
};
module.exports = BlockType;
From e972ebf9b39b1a60a7974efbcb69b6bbdc07665e Mon Sep 17 00:00:00 2001
From: LilyMakesThings <127533508+LilyMakesThings@users.noreply.github.com>
Date: Sun, 25 Feb 2024 07:14:18 +0000
Subject: [PATCH 079/202] bare minimum compat (#33)
---
src/compiler/irgen.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/compiler/irgen.js b/src/compiler/irgen.js
index 6932bb9476a..e667fdf0135 100644
--- a/src/compiler/irgen.js
+++ b/src/compiler/irgen.js
@@ -783,7 +783,7 @@ class ScriptTreeGenerator {
const blockInfo = this.getBlockInfo(block.opcode);
if (blockInfo) {
const type = blockInfo.info.blockType;
- if (type === BlockType.REPORTER || type === BlockType.BOOLEAN || type === BlockType.INLINE) {
+ if (type === BlockType.ARRAY || type === BlockType.OBJECT || type === BlockType.REPORTER || type === BlockType.BOOLEAN || type === BlockType.INLINE) {
return this.descendCompatLayer(block);
}
}
From 9ab989642b6390ec58d0d4c6c20648003252930e Mon Sep 17 00:00:00 2001
From: LilyMakesThings <127533508+LilyMakesThings@users.noreply.github.com>
Date: Fri, 1 Mar 2024 07:34:39 +0000
Subject: [PATCH 080/202] Incorporate more camera into the default blocks (#35)
Hope no one named their sprite "_camera_".
---
src/blocks/scratch3_motion.js | 6 ++++++
src/virtual-machine.js | 2 +-
2 files changed, 7 insertions(+), 1 deletion(-)
diff --git a/src/blocks/scratch3_motion.js b/src/blocks/scratch3_motion.js
index ab2833fa5a7..5779ab59928 100644
--- a/src/blocks/scratch3_motion.js
+++ b/src/blocks/scratch3_motion.js
@@ -97,6 +97,9 @@ class Scratch3MotionBlocks {
if (targetName === '_mouse_') {
targetX = util.ioQuery('mouse', 'getScratchX');
targetY = util.ioQuery('mouse', 'getScratchY');
+ } else if (args.TOWARDS === '_camera_') {
+ targetX = this.runtime.camera.x;
+ targetY = this.runtime.camera.y;
} else if (targetName === '_random_') {
const stageWidth = this.runtime.stageWidth;
const stageHeight = this.runtime.stageHeight;
@@ -148,6 +151,9 @@ class Scratch3MotionBlocks {
if (args.TOWARDS === '_mouse_') {
targetX = util.ioQuery('mouse', 'getScratchX');
targetY = util.ioQuery('mouse', 'getScratchY');
+ } else if (args.TOWARDS === '_camera_') {
+ targetX = this.runtime.camera.x;
+ targetY = this.runtime.camera.y;
} else if (args.TOWARDS === '_random_') {
util.target.setDirection(Math.round(Math.random() * 360) - 180);
return;
diff --git a/src/virtual-machine.js b/src/virtual-machine.js
index baf0cf67fcd..28632017311 100644
--- a/src/virtual-machine.js
+++ b/src/virtual-machine.js
@@ -28,7 +28,7 @@ require('canvas-toBlob');
const {exportCostume} = require('./serialization/tw-costume-import-export');
const Base64Util = require('./util/base64-util');
-const RESERVED_NAMES = ['_mouse_', '_stage_', '_edge_', '_myself_', '_random_'];
+const RESERVED_NAMES = ['_mouse_', '_stage_', '_edge_', '_myself_', '_random_', '_camera_'];
const CORE_EXTENSIONS = [
// 'motion',
From 79667d68e12d1c55c7084697a19a04c558f0970b Mon Sep 17 00:00:00 2001
From: LilyMakesThings <127533508+LilyMakesThings@users.noreply.github.com>
Date: Fri, 1 Mar 2024 08:21:55 +0000
Subject: [PATCH 081/202] fix an oversight
---
src/blocks/scratch3_motion.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/blocks/scratch3_motion.js b/src/blocks/scratch3_motion.js
index 5779ab59928..3aa489b9588 100644
--- a/src/blocks/scratch3_motion.js
+++ b/src/blocks/scratch3_motion.js
@@ -97,7 +97,7 @@ class Scratch3MotionBlocks {
if (targetName === '_mouse_') {
targetX = util.ioQuery('mouse', 'getScratchX');
targetY = util.ioQuery('mouse', 'getScratchY');
- } else if (args.TOWARDS === '_camera_') {
+ } else if (targetName === '_camera_') {
targetX = this.runtime.camera.x;
targetY = this.runtime.camera.y;
} else if (targetName === '_random_') {
From 6dc9e312b14e8162bd168db81a4e6bc20ef75515 Mon Sep 17 00:00:00 2001
From: LilyMakesThings <127533508+LilyMakesThings@users.noreply.github.com>
Date: Fri, 1 Mar 2024 08:29:01 +0000
Subject: [PATCH 082/202] add _camera_ to the "distance to" menu
---
src/blocks/scratch3_sensing.js | 3 +++
1 file changed, 3 insertions(+)
diff --git a/src/blocks/scratch3_sensing.js b/src/blocks/scratch3_sensing.js
index d5eba4a1792..2da4eef0e3b 100644
--- a/src/blocks/scratch3_sensing.js
+++ b/src/blocks/scratch3_sensing.js
@@ -203,6 +203,9 @@ class Scratch3SensingBlocks {
if (args.DISTANCETOMENU === '_mouse_') {
targetX = util.ioQuery('mouse', 'getScratchX');
targetY = util.ioQuery('mouse', 'getScratchY');
+ if (args.DISTANCETOMENU === '_camera_') {
+ targetX = this.runtime.camera.x;
+ targetY = this.runtime.camera.y;
} else {
args.DISTANCETOMENU = Cast.toString(args.DISTANCETOMENU);
const distTarget = this.runtime.getSpriteTargetByName(
From 350c27e9ecdcd5c40bdd72d15ec299fff9807262 Mon Sep 17 00:00:00 2001
From: LilyMakesThings <127533508+LilyMakesThings@users.noreply.github.com>
Date: Fri, 1 Mar 2024 08:43:41 +0000
Subject: [PATCH 083/202] fix a silly syntax error
---
src/blocks/scratch3_sensing.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/blocks/scratch3_sensing.js b/src/blocks/scratch3_sensing.js
index 2da4eef0e3b..7152c62d406 100644
--- a/src/blocks/scratch3_sensing.js
+++ b/src/blocks/scratch3_sensing.js
@@ -203,7 +203,7 @@ class Scratch3SensingBlocks {
if (args.DISTANCETOMENU === '_mouse_') {
targetX = util.ioQuery('mouse', 'getScratchX');
targetY = util.ioQuery('mouse', 'getScratchY');
- if (args.DISTANCETOMENU === '_camera_') {
+ } else if (args.DISTANCETOMENU === '_camera_') {
targetX = this.runtime.camera.x;
targetY = this.runtime.camera.y;
} else {
From c6991b9ab9d781c2faf0786e08db8f6fd1d9136b Mon Sep 17 00:00:00 2001
From: LilyMakesThings <127533508+LilyMakesThings@users.noreply.github.com>
Date: Fri, 1 Mar 2024 09:06:07 +0000
Subject: [PATCH 084/202] Add an extra note for distance
---
src/compiler/jsgen.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/compiler/jsgen.js b/src/compiler/jsgen.js
index 4c427d7c7e5..0bedd69cbd9 100644
--- a/src/compiler/jsgen.js
+++ b/src/compiler/jsgen.js
@@ -703,7 +703,7 @@ class JSGenerator {
case 'sensing.daysSince2000':
return new TypedInput('daysSince2000()', TYPE_NUMBER);
case 'sensing.distance':
- // TODO: on stages, this can be computed at compile time
+ // TODO: on stages and invalid values, this can be computed at compile time
return new TypedInput(`distance(${this.descendInput(node.target).asString()})`, TYPE_NUMBER);
case 'sensing.hour':
return new TypedInput(`(new Date().getHours())`, TYPE_NUMBER);
From 8657f0da6491ccd1ad43c4f71aca85846cf25a57 Mon Sep 17 00:00:00 2001
From: LilyMakesThings <127533508+LilyMakesThings@users.noreply.github.com>
Date: Fri, 1 Mar 2024 09:07:53 +0000
Subject: [PATCH 085/202] Add extra distance calc to compiler too
---
src/compiler/jsexecute.js | 3 +++
1 file changed, 3 insertions(+)
diff --git a/src/compiler/jsexecute.js b/src/compiler/jsexecute.js
index 247b3e2c606..71f72b0350d 100644
--- a/src/compiler/jsexecute.js
+++ b/src/compiler/jsexecute.js
@@ -377,6 +377,9 @@ runtimeFunctions.distance = `const distance = menu => {
if (menu === '_mouse_') {
targetX = thread.target.runtime.ioDevices.mouse.getScratchX();
targetY = thread.target.runtime.ioDevices.mouse.getScratchY();
+ } else if (menu === '_camera_') {
+ targetX = thread.target.runtime.camera.x;
+ targetY = thread.target.runtime.camera.y;
} else {
const distTarget = thread.target.runtime.getSpriteTargetByName(menu);
if (!distTarget) return 10000;
From f3612befcdf7ee188b4ebd3bd06a013ad9b2a035 Mon Sep 17 00:00:00 2001
From: LilyMakesThings <127533508+LilyMakesThings@users.noreply.github.com>
Date: Mon, 4 Mar 2024 03:51:43 +0000
Subject: [PATCH 086/202] Boring compiler compatibility for broken blocks (#36)
---
src/blocks/scratch3_string.js | 38 ++++++++++++++++++----------------
src/compiler/compat-blocks.js | 6 ++++++
src/compiler/irgen.js | 39 ++++++-----------------------------
src/compiler/jsgen.js | 12 ++---------
4 files changed, 34 insertions(+), 61 deletions(-)
diff --git a/src/blocks/scratch3_string.js b/src/blocks/scratch3_string.js
index a8bda69fea0..59c70a3fb43 100644
--- a/src/blocks/scratch3_string.js
+++ b/src/blocks/scratch3_string.js
@@ -95,21 +95,23 @@ class Scratch3StringBlocks {
itemSplit (args) { // usb
const str = Cast.toString(args.STRING).toLowerCase();
- const split = Cast.toString(args.SPLIT).toLowerCase();
+ const split = Cast.toString(args.SPLIT);
- return this._getIndexFromSplit(str, split, args.INDEX);
+ return this._getItemFromSplit(str, split, args.INDEX);
}
- _getIndexFromSplit (string, split, index) { // used by compiler
+ _getItemFromSplit (string, split, index) { // used by compiler
+ const splitString = string.split(split);
+
if (index === "last") {
- index = string.length - 1;
+ index = splitString.length - 1;
} else if (index === "random") {
- index = Math.floor(Math.random()*string.length);
+ index = Math.floor(Math.random()*splitString.length);
} else {
index = Cast.toNumber(index) - 1;
}
- return string.split(split)[index] ?? 0;
+ return splitString[index] ?? "";
}
ternary (args) { // usb
@@ -142,25 +144,25 @@ class Scratch3StringBlocks {
return this._getNumberIndex(find, str, args.INDEX);
}
- _getNumberIndex (find, string, index) { // used by compile
- if (index === "last") {
- index = string.length - 1;
- } else if (index === "random") {
- index = Math.floor(Math.random()*string.length);
- } else {
- index = Cast.toNumber(index) - 1;
- }
-
- const length = find.length - 1;
- if (length > string) return 0;
+ _getNumberIndex (find, string, index) { // used by compiler
+ const length = find.length;
+ if (length > string.length) return 0;
let occurences = [];
- for (let i = 0; i > string.length; i++) {
+ for (let i = 0; i < string.length; i++) {
if (string.substring(i, i + length) === find) {
occurences.push(i);
}
}
+ if (index === "last") {
+ index = occurences.length - 1;
+ } else if (index === "random") {
+ index = Math.floor(Math.random()*occurences.length);
+ } else {
+ index = Cast.toNumber(index) - 1;
+ }
+
return occurences[index] ?? 0;
}
diff --git a/src/compiler/compat-blocks.js b/src/compiler/compat-blocks.js
index b3766cc8262..9fffe2f7fb4 100644
--- a/src/compiler/compat-blocks.js
+++ b/src/compiler/compat-blocks.js
@@ -51,6 +51,12 @@ const inputs = [
'sensing_loudness',
'sensing_userid',
'sound_volume',
+
+ 'operator_letter_of',
+ 'string_item_split',
+ 'string_convert',
+ 'string_index_of',
+ 'string_ternary',
];
module.exports = {
diff --git a/src/compiler/irgen.js b/src/compiler/irgen.js
index e667fdf0135..6dfd0698769 100644
--- a/src/compiler/irgen.js
+++ b/src/compiler/irgen.js
@@ -391,12 +391,12 @@ class ScriptTreeGenerator {
kind: 'op.length',
string: this.descendInputOfBlock(block, 'STRING')
};
- case 'operator_letter_of':
- return {
- kind: 'op.letterOf',
- letter: this.descendInputOfBlock(block, 'LETTER'),
- string: this.descendInputOfBlock(block, 'STRING')
- };
+// case 'operator_letter_of':
+// return {
+// kind: 'op.letterOf',
+// letter: this.descendInputOfBlock(block, 'LETTER'),
+// string: this.descendInputOfBlock(block, 'STRING')
+// };
case 'operator_letters_of':
return {
kind: 'op.lettersOf',
@@ -702,38 +702,18 @@ class ScriptTreeGenerator {
kind: 'sensing.username'
};
- case 'string_convert':
- return {
- kind: 'str.convert',
- left: this.descendInputOfBlock(block, 'STRING'),
- right: block.fields.CONVERT.value
- };
case 'string_exactly':
return {
kind: 'str.exactly',
left: this.descendInputOfBlock(block, 'STRING1'),
right: this.descendInputOfBlock(block, 'STRING2')
};
- case 'string_index_of':
- return {
- kind: 'str.index',
- num: this.descendInputOfBlock(block, 'INDEX'),
- left: this.descendInputOfBlock(block, 'STRING1'),
- right: this.descendInputOfBlock(block, 'STRING2')
- };
case 'string_is':
return {
kind: 'str.is',
left: this.descendInputOfBlock(block, 'STRING'),
right: block.fields.CONVERT.value
};
- case 'string_item_split':
- return {
- kind: 'str.split',
- num: this.descendInputOfBlock(block, 'INDEX'),
- str: this.descendInputOfBlock(block, 'STRING'),
- split: this.descendInputOfBlock(block, 'SPLIT')
- };
case 'string_repeat':
return {
kind: 'str.repeat',
@@ -752,13 +732,6 @@ class ScriptTreeGenerator {
kind: 'str.reverse',
str: this.descendInputOfBlock(block, 'STRING')
};
- case 'string_ternary':
- return {
- kind: 'str.convert',
- operand: this.descendInputOfBlock(block, 'CONDITION'),
- left: this.descendInputOfBlock(block, 'STRING1'),
- right: this.descendInputOfBlock(block, 'STRING2')
- };
case 'sound_sounds_menu':
// This menu is special compared to other menus -- it actually has an opcode function.
diff --git a/src/compiler/jsgen.js b/src/compiler/jsgen.js
index 0bedd69cbd9..fce7ca281f2 100644
--- a/src/compiler/jsgen.js
+++ b/src/compiler/jsgen.js
@@ -609,8 +609,8 @@ class JSGenerator {
const right = this.descendInput(node.right);
return new TypedInput(`(${left.asUnknown()} <= ${right.asUnknown()})`, TYPE_BOOLEAN);
}
- case 'op.letterOf':
- return new TypedInput(`((${this.descendInput(node.string).asString()})[(${this.descendInput(node.letter).asNumber()} | 0) - 1] || "")`, TYPE_STRING);
+// case 'op.letterOf':
+// return new TypedInput(`((${this.descendInput(node.string).asString()})[(${this.descendInput(node.letter).asNumber()} | 0) - 1] || "")`, TYPE_STRING);
case 'op.lettersOf':
return new TypedInput(`((${this.descendInput(node.string).asString()}).substring(${this.descendInput(node.left).asNumber() - 1}, ${this.descendInput(node.right).asNumber()}) || "")`, TYPE_STRING);
case 'op.ln':
@@ -762,12 +762,8 @@ class JSGenerator {
case 'sensing.year':
return new TypedInput(`(new Date().getFullYear())`, TYPE_NUMBER);
- case 'str.convert':
- return new TypedInput(`runtime.ext_scratch3_string._convertString(${this.descendInput(node.left).asString()}, ${node.right})`, TYPE_STRING);
case 'str.exactly':
return new TypedInput(`(${this.descendInput(node.left).asUnknown()} === ${this.descendInput(node.right).asUnknown()})`, TYPE_UNKNOWN);
- case 'str.index':
- return new TypedInput(`runtime.ext_scratch3_string._getNumberIndex(${this.descendInput(node.left).asString()}, ${this.descendInput(node.right).asString()}, ${this.descendInput(node.num).asNumber()})`, TYPE_NUMBER);
case 'str.is': {
const str = this.descendInput(node.left).asString();
if (node.right.toLowerCase() === "uppercase") {
@@ -776,16 +772,12 @@ class JSGenerator {
return new TypedInput(`${str.toLowerCase() === str}`, TYPE_BOOLEAN);
}
}
- case 'str.split':
- return new TypedInput(`runtime.ext_scratch3_string._getIndexFromSplit(${this.descendInput(node.str).asString()}, ${this.descendInput(node.split).asString()}, ${this.descendInput(node.num).asNumber()})`, TYPE_STRING);
case 'str.repeat':
return new TypedInput(`(${this.descendInput(node.str).asString()}.repeat(${this.descendInput(node.num).asNumber()}))`, TYPE_STRING);
case 'str.replace':
return new TypedInput(`${this.descendInput(node.str).asString()}.replace(new RegExp(${this.descendInput(node.left).asString()}, "gi"), ${this.descendInput(node.right).asString()})`, TYPE_STRING);
case 'str.reverse':
return new TypedInput(`${this.descendInput(node.str).asString()}.split("").reverse().join("");`, TYPE_STRING);
- case 'str.ternary':
- return new TypedInput(`(${this.descendInput(node.operand).asBoolean()} ? ${this.descendInput(node.left).asString()} : ${this.descendInput(node.right).asString()})`, TYPE_UNKNOWN);
case 'camera.x':
return new TypedInput('runtime.camera.x', TYPE_NUMBER);
From b24fe6a489cb1a35b67d08a1fd3f4eb30aff0240 Mon Sep 17 00:00:00 2001
From: LilyMakesThings <127533508+LilyMakesThings@users.noreply.github.com>
Date: Fri, 8 Mar 2024 14:44:54 +0000
Subject: [PATCH 087/202] Fix minor oversight
When changing field_label into field_label_serializable, I only did it for 1 of the 3 places that needed it.
---
src/engine/runtime.js | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/engine/runtime.js b/src/engine/runtime.js
index 291d9229508..db2594b4e9b 100644
--- a/src/engine/runtime.js
+++ b/src/engine/runtime.js
@@ -1687,8 +1687,8 @@ class Runtime extends EventEmitter {
// check if this is not one of those cases. E.g. an inline image on a block.
if (argTypeInfo.fieldType === 'field_image') {
argJSON = this._constructInlineImageJson(argInfo);
- } else if (argTypeInfo.fieldType === 'field_label') {
- argJSON.type = 'field_label';
+ } else if (argTypeInfo.fieldType === 'field_label_serializable') {
+ argJSON.type = 'field_label_serializable';
argJSON.text = argInfo.text;
} else if (argTypeInfo.fieldType === 'field_variable') {
argJSON = this._constructVariableJson(argInfo, placeholder);
From 64b90a54b26ba4c29c9491b4e2dc51f110f7a187 Mon Sep 17 00:00:00 2001
From: LilyMakesThings <127533508+LilyMakesThings@users.noreply.github.com>
Date: Fri, 8 Mar 2024 15:12:22 +0000
Subject: [PATCH 088/202] Various changes
- Don't hardcode width/height for inline images
- Create constructor for label fields
---
src/engine/runtime.js | 24 ++++++++++++++++++------
1 file changed, 18 insertions(+), 6 deletions(-)
diff --git a/src/engine/runtime.js b/src/engine/runtime.js
index db2594b4e9b..3a5f3ec6ee2 100644
--- a/src/engine/runtime.js
+++ b/src/engine/runtime.js
@@ -1631,9 +1631,8 @@ class Runtime extends EventEmitter {
return {
type: 'field_image',
src: argInfo.dataURI || '',
- // TODO these probably shouldn't be hardcoded...?
- width: 24,
- height: 24,
+ width: argInfo.width || 24,
+ height: argInfo.height || 24,
// Whether or not the inline image should be flipped horizontally
// in RTL languages. Defaults to false, indicating that the
// image will not be flipped.
@@ -1643,7 +1642,7 @@ class Runtime extends EventEmitter {
/**
* Helper for _convertPlaceholders which handles variable fields which are a specialized case of block "arguments".
- * @param {object} argInfo Metadata about the inline image as specified by the extension
+ * @param {object} argInfo Metadata about the variable dropdown as specified by the extension
* @return {object} JSON blob for a scratch-blocks variable field.
* @private
*/
@@ -1660,6 +1659,20 @@ class Runtime extends EventEmitter {
};
}
+ /**
+ * Helper for _convertPlaceholders which handles label fields which are a specialized case of block "arguments".
+ * @param {object} argInfo Metadata about the label as specified by the extension
+ * @return {object} JSON blob for a scratch-blocks label field.
+ * @private
+ */
+ _constructLabelJson (argInfo, placeholder) {
+ return {
+ type: 'field_label_serializable',
+ name: placeholder,
+ text: argInfo.defaultValue,
+ };
+ }
+
/**
* Helper for _convertForScratchBlocks which handles linearization of argument placeholders. Called as a callback
* from string#replace. In addition to the return value the JSON and XML items in the context will be filled.
@@ -1688,8 +1701,7 @@ class Runtime extends EventEmitter {
if (argTypeInfo.fieldType === 'field_image') {
argJSON = this._constructInlineImageJson(argInfo);
} else if (argTypeInfo.fieldType === 'field_label_serializable') {
- argJSON.type = 'field_label_serializable';
- argJSON.text = argInfo.text;
+ argJSON = this._constructLabelJson(argInfo);
} else if (argTypeInfo.fieldType === 'field_variable') {
argJSON = this._constructVariableJson(argInfo, placeholder);
} else {
From 8e9a18bc1490450ae835b0374dda5efde9c541ca Mon Sep 17 00:00:00 2001
From: LilyMakesThings <127533508+LilyMakesThings@users.noreply.github.com>
Date: Sun, 21 Apr 2024 04:11:35 +0100
Subject: [PATCH 089/202] Update sb3.js
---
src/serialization/sb3.js | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/src/serialization/sb3.js b/src/serialization/sb3.js
index b760e05bb08..7c0f90fbf40 100644
--- a/src/serialization/sb3.js
+++ b/src/serialization/sb3.js
@@ -789,6 +789,13 @@ const serialize = function (runtime, targetId, {allowOptimization = true} = {})
meta.origin = runtime.origin;
}
+ // USB: A few mods have agreed to list our platform's name inside of the project json.
+ // We also add a couple more bits specific to Unsandboxed.
+ const platformMeta = Object.create(null);
+ platformMeta.name = "Unsandboxed";
+ platformMeta.version = "unsandboxed-alpha";
+ meta.platform = platformMeta;
+
// Attach full user agent string to metadata if available
meta.agent = '';
// TW: Never include full user agent to slightly improve user privacy
From 8ca425d3e946f0bf4c6b0bb346d0d73bcf42e972 Mon Sep 17 00:00:00 2001
From: LilyMakesThings <127533508+LilyMakesThings@users.noreply.github.com>
Date: Sun, 21 Apr 2024 04:18:02 +0100
Subject: [PATCH 090/202] Update sb3.js
---
src/serialization/sb3.js | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/src/serialization/sb3.js b/src/serialization/sb3.js
index 7c0f90fbf40..14fb90d6ac2 100644
--- a/src/serialization/sb3.js
+++ b/src/serialization/sb3.js
@@ -793,7 +793,8 @@ const serialize = function (runtime, targetId, {allowOptimization = true} = {})
// We also add a couple more bits specific to Unsandboxed.
const platformMeta = Object.create(null);
platformMeta.name = "Unsandboxed";
- platformMeta.version = "unsandboxed-alpha";
+ platformMeta.url = "https://unsandboxed.org/";
+ platformMeta.version = "alpha";
meta.platform = platformMeta;
// Attach full user agent string to metadata if available
From 5555a85e5b1e929075504aa84585215ee68b5fe0 Mon Sep 17 00:00:00 2001
From: LilyMakesThings <127533508+LilyMakesThings@users.noreply.github.com>
Date: Sun, 21 Apr 2024 21:22:14 +0100
Subject: [PATCH 091/202] Remove some unnecessary junk
---
src/blocks/scratch3_camera.js | 18 ++++++++----------
1 file changed, 8 insertions(+), 10 deletions(-)
diff --git a/src/blocks/scratch3_camera.js b/src/blocks/scratch3_camera.js
index 29fe16def24..399ff987528 100644
--- a/src/blocks/scratch3_camera.js
+++ b/src/blocks/scratch3_camera.js
@@ -1,6 +1,4 @@
const Cast = require('../util/cast');
-const MathUtil = require('../util/math-util');
-const Timer = require('../util/timer');
class Scratch3CameraBlocks {
constructor (runtime) {
@@ -39,13 +37,13 @@ class Scratch3CameraBlocks {
};
}
- moveToXY (args, util) {
+ moveToXY (args) {
const x = Cast.toNumber(args.X);
const y = Cast.toNumber(args.Y);
this.runtime.camera.setXY(x, y);
}
- changeByXY (args, util) {
+ changeByXY (args) {
const x = Cast.toNumber(args.X);
const y = Cast.toNumber(args.Y);
const newX = x + this.runtime.camera.x;
@@ -53,33 +51,33 @@ class Scratch3CameraBlocks {
this.runtime.camera.setXY(newX, newY);
}
- setX (args, util) {
+ setX (args) {
const x = Cast.toNumber(args.X);
this.runtime.camera.setXY(x, this.runtime.camera.y);
}
- changeX (args, util) {
+ changeX (args) {
const x = Cast.toNumber(args.X);
const newX = x + this.runtime.camera.x;
this.runtime.camera.setXY(newX, this.runtime.camera.y);
}
- setY (args, util) {
+ setY (args) {
const y = Cast.toNumber(args.Y);
this.runtime.camera.setXY(this.runtime.camera.x, y);
}
- changeY (args, util) {
+ changeY (args) {
const y = Cast.toNumber(args.Y);
const newY = y + this.runtime.camera.y;
this.runtime.camera.setXY(this.runtime.camera.x, newY);
}
- getCameraX (args, util) {
+ getCameraX () {
return this.runtime.camera.x;
}
- getCameraY (args, util) {
+ getCameraY () {
return this.runtime.camera.y;
}
}
From 88d053d8f83047c50d8c4f7b2ce2e5e7f44e6770 Mon Sep 17 00:00:00 2001
From: LilyMakesThings <127533508+LilyMakesThings@users.noreply.github.com>
Date: Sun, 21 Apr 2024 21:25:02 +0100
Subject: [PATCH 092/202] Update scratch3_event.js
---
src/blocks/scratch3_event.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/blocks/scratch3_event.js b/src/blocks/scratch3_event.js
index cf598829f43..5bfad8217f7 100644
--- a/src/blocks/scratch3_event.js
+++ b/src/blocks/scratch3_event.js
@@ -67,7 +67,7 @@ class Scratch3EventBlocks {
};
}
- when (args, util) {
+ when (args) {
const condition = Cast.toBoolean(args.CONDITION);
return condition;
}
From c39ed7b836c7ed7d24a48c5cebf20667efde94b4 Mon Sep 17 00:00:00 2001
From: LilyMakesThings <127533508+LilyMakesThings@users.noreply.github.com>
Date: Sun, 21 Apr 2024 21:28:10 +0100
Subject: [PATCH 093/202] Update scratch3_operators.js
---
src/blocks/scratch3_operators.js | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/src/blocks/scratch3_operators.js b/src/blocks/scratch3_operators.js
index f710efb0355..3c0bbf1199d 100644
--- a/src/blocks/scratch3_operators.js
+++ b/src/blocks/scratch3_operators.js
@@ -115,9 +115,8 @@ class Scratch3OperatorsBlocks {
if (from > to) {
return Math.min(Math.max(n, to), from);
- } else {
- return Math.min(Math.max(n, from), to);
}
+ return Math.min(Math.max(n, from), to);
}
random (args) {
From c4e7ca7b54f2fdc50055ee3ea2ec5c8b6cd7ce81 Mon Sep 17 00:00:00 2001
From: LilyMakesThings <127533508+LilyMakesThings@users.noreply.github.com>
Date: Sun, 21 Apr 2024 21:35:16 +0100
Subject: [PATCH 094/202] Fix some formatting and unused stuff
---
src/blocks/scratch3_string.js | 40 +++++++++++++++++------------------
1 file changed, 20 insertions(+), 20 deletions(-)
diff --git a/src/blocks/scratch3_string.js b/src/blocks/scratch3_string.js
index 59c70a3fb43..ca26bd78f5d 100644
--- a/src/blocks/scratch3_string.js
+++ b/src/blocks/scratch3_string.js
@@ -1,5 +1,4 @@
const Cast = require('../util/cast.js');
-const MathUtil = require('../util/math-util.js');
class Scratch3StringBlocks {
constructor (runtime) {
@@ -44,7 +43,9 @@ class Scratch3StringBlocks {
reverse (args) { // usb
const str = Cast.toString(args.STRING);
- return str.split("").reverse().join("");
+ return str.split('')
+ .reverse()
+ .join('');
}
repeat (args) { // usb
@@ -59,7 +60,7 @@ class Scratch3StringBlocks {
const replacer = Cast.toString(args.WITH);
const str = Cast.toString(args.STRING);
- return str.replace(new RegExp(old, "gi"), replacer);
+ return str.replace(new RegExp(old, 'gi'), replacer);
}
letterOf (args) {
@@ -69,10 +70,10 @@ class Scratch3StringBlocks {
_getLetterOf (string, index) { // usb // used by compiler
// usb: we support some weird dropdowns now
- if (index === "last") {
+ if (index === 'last') {
index = string.length - 1;
- } else if (index === "random") {
- index = Math.floor(Math.random()*string.length);
+ } else if (index === 'random') {
+ index = Math.floor(Math.random() * string.length);
} else {
index = Cast.toNumber(index) - 1;
}
@@ -103,15 +104,15 @@ class Scratch3StringBlocks {
_getItemFromSplit (string, split, index) { // used by compiler
const splitString = string.split(split);
- if (index === "last") {
+ if (index === 'last') {
index = splitString.length - 1;
- } else if (index === "random") {
- index = Math.floor(Math.random()*splitString.length);
+ } else if (index === 'random') {
+ index = Math.floor(Math.random() * splitString.length);
} else {
index = Cast.toNumber(index) - 1;
}
- return splitString[index] ?? "";
+ return splitString[index] ?? '';
}
ternary (args) { // usb
@@ -130,11 +131,11 @@ class Scratch3StringBlocks {
}
_convertString (string, textCase) { // used by compiler
- if (textCase === "lowercase") {
+ if (textCase === 'lowercase') {
return string.toLowerCase();
- } else {
- return string.toUpperCase();
}
+
+ return string.toUpperCase();
}
indexOf (args) { // usb
@@ -148,17 +149,17 @@ class Scratch3StringBlocks {
const length = find.length;
if (length > string.length) return 0;
- let occurences = [];
+ const occurences = [];
for (let i = 0; i < string.length; i++) {
if (string.substring(i, i + length) === find) {
occurences.push(i);
}
}
- if (index === "last") {
+ if (index === 'last') {
index = occurences.length - 1;
- } else if (index === "random") {
- index = Math.floor(Math.random()*occurences.length);
+ } else if (index === 'random') {
+ index = Math.floor(Math.random() * occurences.length);
} else {
index = Cast.toNumber(index) - 1;
}
@@ -183,11 +184,10 @@ class Scratch3StringBlocks {
const str = Cast.toString(args.STRING);
const check = Cast.toString(args.CONVERT).toLowerCase();
- if (check === "lowercase") {
+ if (check === 'lowercase') {
return str.toLowerCase() === str;
- } else {
- return str.toUpperCase() === str;
}
+ return str.toUpperCase() === str;
}
}
From 703d08cc11f9f17b644c891f7c632fbf42c0f176 Mon Sep 17 00:00:00 2001
From: LilyMakesThings <127533508+LilyMakesThings@users.noreply.github.com>
Date: Mon, 22 Apr 2024 00:01:36 +0100
Subject: [PATCH 095/202] Update compat-blocks.js
---
src/compiler/compat-blocks.js | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/compiler/compat-blocks.js b/src/compiler/compat-blocks.js
index 9fffe2f7fb4..fe6825e04f0 100644
--- a/src/compiler/compat-blocks.js
+++ b/src/compiler/compat-blocks.js
@@ -40,7 +40,7 @@ const stacked = [
'sound_playuntildone',
'sound_seteffectto',
'sound_setvolumeto',
- 'sound_stopallsounds',
+ 'sound_stopallsounds'
];
const inputs = [
@@ -56,7 +56,7 @@ const inputs = [
'string_item_split',
'string_convert',
'string_index_of',
- 'string_ternary',
+ 'string_ternary'
];
module.exports = {
From 7a4c69b3fe11fa0b0b4aa6abb4215a29d8bf1c0e Mon Sep 17 00:00:00 2001
From: LilyMakesThings <127533508+LilyMakesThings@users.noreply.github.com>
Date: Mon, 22 Apr 2024 00:02:38 +0100
Subject: [PATCH 096/202] Update irgen.js
---
src/compiler/irgen.js | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
diff --git a/src/compiler/irgen.js b/src/compiler/irgen.js
index 6dfd0698769..c3acc17422c 100644
--- a/src/compiler/irgen.js
+++ b/src/compiler/irgen.js
@@ -391,12 +391,12 @@ class ScriptTreeGenerator {
kind: 'op.length',
string: this.descendInputOfBlock(block, 'STRING')
};
-// case 'operator_letter_of':
-// return {
-// kind: 'op.letterOf',
-// letter: this.descendInputOfBlock(block, 'LETTER'),
-// string: this.descendInputOfBlock(block, 'STRING')
-// };
+ // case 'operator_letter_of':
+ // return {
+ // kind: 'op.letterOf',
+ // letter: this.descendInputOfBlock(block, 'LETTER'),
+ // string: this.descendInputOfBlock(block, 'STRING')
+ // };
case 'operator_letters_of':
return {
kind: 'op.lettersOf',
From aa6c005785b86c4e913c7d4388e8a834ac38584a Mon Sep 17 00:00:00 2001
From: LilyMakesThings <127533508+LilyMakesThings@users.noreply.github.com>
Date: Mon, 22 Apr 2024 00:03:52 +0100
Subject: [PATCH 097/202] Update irgen.js
---
src/compiler/irgen.js | 9 +++++++--
1 file changed, 7 insertions(+), 2 deletions(-)
diff --git a/src/compiler/irgen.js b/src/compiler/irgen.js
index c3acc17422c..1ba316380d0 100644
--- a/src/compiler/irgen.js
+++ b/src/compiler/irgen.js
@@ -756,7 +756,12 @@ class ScriptTreeGenerator {
const blockInfo = this.getBlockInfo(block.opcode);
if (blockInfo) {
const type = blockInfo.info.blockType;
- if (type === BlockType.ARRAY || type === BlockType.OBJECT || type === BlockType.REPORTER || type === BlockType.BOOLEAN || type === BlockType.INLINE) {
+ if (type === BlockType.ARRAY
+ || type === BlockType.OBJECT
+ || type === BlockType.REPORTER
+ || type === BlockType.BOOLEAN
+ || type === BlockType.INLINE
+ ) {
return this.descendCompatLayer(block);
}
}
@@ -794,7 +799,7 @@ class ScriptTreeGenerator {
kind: 'constant',
value: true
},
- code: this.descendSubstack(block, 'SUBSTACK'),
+ code: this.descendSubstack(block, 'SUBSTACK')
};
case 'control_clear_counter':
return {
From 9cfd09105df93bde927ccabe23705d32c82ef9d1 Mon Sep 17 00:00:00 2001
From: LilyMakesThings <127533508+LilyMakesThings@users.noreply.github.com>
Date: Mon, 22 Apr 2024 00:04:38 +0100
Subject: [PATCH 098/202] Update jsgen.js
---
src/compiler/jsgen.js | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/compiler/jsgen.js b/src/compiler/jsgen.js
index fce7ca281f2..9dbc3cd724b 100644
--- a/src/compiler/jsgen.js
+++ b/src/compiler/jsgen.js
@@ -609,8 +609,8 @@ class JSGenerator {
const right = this.descendInput(node.right);
return new TypedInput(`(${left.asUnknown()} <= ${right.asUnknown()})`, TYPE_BOOLEAN);
}
-// case 'op.letterOf':
-// return new TypedInput(`((${this.descendInput(node.string).asString()})[(${this.descendInput(node.letter).asNumber()} | 0) - 1] || "")`, TYPE_STRING);
+ //case 'op.letterOf':
+ // return new TypedInput(`((${this.descendInput(node.string).asString()})[(${this.descendInput(node.letter).asNumber()} | 0) - 1] || "")`, TYPE_STRING);
case 'op.lettersOf':
return new TypedInput(`((${this.descendInput(node.string).asString()}).substring(${this.descendInput(node.left).asNumber() - 1}, ${this.descendInput(node.right).asNumber()}) || "")`, TYPE_STRING);
case 'op.ln':
From 7ec34b32a37f3beeb38eed0f224a977ba7ffa940 Mon Sep 17 00:00:00 2001
From: LilyMakesThings <127533508+LilyMakesThings@users.noreply.github.com>
Date: Mon, 22 Apr 2024 00:07:56 +0100
Subject: [PATCH 099/202] Update jsgen.js
---
src/compiler/jsgen.js | 5 ++---
1 file changed, 2 insertions(+), 3 deletions(-)
diff --git a/src/compiler/jsgen.js b/src/compiler/jsgen.js
index 9dbc3cd724b..03402bbf795 100644
--- a/src/compiler/jsgen.js
+++ b/src/compiler/jsgen.js
@@ -766,11 +766,10 @@ class JSGenerator {
return new TypedInput(`(${this.descendInput(node.left).asUnknown()} === ${this.descendInput(node.right).asUnknown()})`, TYPE_UNKNOWN);
case 'str.is': {
const str = this.descendInput(node.left).asString();
- if (node.right.toLowerCase() === "uppercase") {
+ if (node.right.toLowerCase() === 'uppercase') {
return new TypedInput(`${str.toUpperCase() === str}`, TYPE_BOOLEAN);
- } else {
- return new TypedInput(`${str.toLowerCase() === str}`, TYPE_BOOLEAN);
}
+ return new TypedInput(`${str.toLowerCase() === str}`, TYPE_BOOLEAN);
}
case 'str.repeat':
return new TypedInput(`(${this.descendInput(node.str).asString()}.repeat(${this.descendInput(node.num).asNumber()}))`, TYPE_STRING);
From 876c8a4f7826521ea06bb2072390fa59d077975c Mon Sep 17 00:00:00 2001
From: LilyMakesThings <127533508+LilyMakesThings@users.noreply.github.com>
Date: Mon, 22 Apr 2024 00:10:45 +0100
Subject: [PATCH 100/202] Update camera.js
---
src/engine/camera.js | 14 +++++++-------
1 file changed, 7 insertions(+), 7 deletions(-)
diff --git a/src/engine/camera.js b/src/engine/camera.js
index 3f35004cb6e..44d7cc4aee8 100644
--- a/src/engine/camera.js
+++ b/src/engine/camera.js
@@ -67,7 +67,7 @@ class Camera extends EventEmitter {
* @param x The x coordinate.
* @param y The y coordinate.
*/
- setXY(x, y) {
+ setXY (x, y) {
this.x = Cast.toNumber(x);
this.y = Cast.toNumber(y);
@@ -78,7 +78,7 @@ class Camera extends EventEmitter {
* Set the zoom of the camera.
* @param zoom The new zoom value.
*/
- setZoom(zoom) {
+ setZoom (zoom) {
this.zoom = Cast.toNumber(zoom);
if (this.runtime.runtimeOptions.miscLimits) {
this.zoom = MathUtil.clamp(this.zoom, 10, 300);
@@ -91,7 +91,7 @@ class Camera extends EventEmitter {
* Point the camera towards a given direction.
* @param direction Direction to point the camera.
*/
- setDirection(direction) {
+ setDirection (direction) {
if (!isFinite(direction)) return;
this.direction = MathUtil.wrapClamp(direction, -179, 180);
@@ -103,14 +103,14 @@ class Camera extends EventEmitter {
* Set whether the camera will affect the projection.
* @param enabled The new enabled state.
*/
- setEnabled(enabled) {
+ setEnabled (enabled) {
this.enabled = enabled;
}
/**
* Tell the renderer to update the rendered camera state.
*/
- emitCameraUpdate() {
+ emitCameraUpdate () {
if (!this.runtime.renderer) return;
this.runtime.renderer._updateCamera(
@@ -118,7 +118,7 @@ class Camera extends EventEmitter {
this.y,
this.direction,
this.zoom
- )
+ );
this.runtime.requestRedraw();
}
@@ -126,7 +126,7 @@ class Camera extends EventEmitter {
/**
* Reset all camera properties.
*/
- reset() {
+ reset () {
this.x = 0;
this.y = 0;
this.direction = 90;
From 3dbd30f85f886a939af8ae543f5a033bb4b129e3 Mon Sep 17 00:00:00 2001
From: LilyMakesThings <127533508+LilyMakesThings@users.noreply.github.com>
Date: Sat, 27 Apr 2024 13:57:43 +0100
Subject: [PATCH 101/202] Expose more utils (#39)
---
src/util/usb-util.js | 44 ++++++++++++++++++++++++++++++++++----------
1 file changed, 34 insertions(+), 10 deletions(-)
diff --git a/src/util/usb-util.js b/src/util/usb-util.js
index eced66e6f5a..a316e9cadbd 100644
--- a/src/util/usb-util.js
+++ b/src/util/usb-util.js
@@ -2,22 +2,46 @@ const Base64Util = require('../util/base64-util');
const Cast = require('../util/cast');
const Clone = require('../util/clone');
const Color = require('../util/color');
+const {fetchWithTimeout} = require('../util/fetch-with-timeout');
+const getMonitorID = require('../util/get-monitor-id');
+const JSONRPC = require('../util/get-monitor-id');
+const log = require('../util/log');
const MathUtil = require('../util/math-util');
+const maybeFormatMessage = require('../util/maybe-format-message');
+const newBlockIDs = require('../util/new-block-ids');
+const RateLimiter = require('../util/rateLimiter');
+const ScratchLinkWebsocket = require('../util/scratch-link-websocket');
const StringUtil = require('../util/string-util');
+const taskQueue = require('../util/task-queue');
const Timer = require('../util/timer');
+const AssetUtil = require('../util/tw-asset-util');
+const StaticFetch = require('../util/tw-static-fetch');
const uid = require('../util/uid');
+const VariableUtil = require('../util/variable-util');
const xmlEscape = require('../util/xml-escape');
const Util = {
- Base64Util,
- Cast,
- Clone,
- Color,
- MathUtil,
- StringUtil,
- Timer,
- uid,
- xmlEscape
-}
+ Base64Util,
+ Cast,
+ Clone,
+ Color,
+ fetchWithTimeout,
+ getMonitorID,
+ JSONRPC,
+ log,
+ MathUtil,
+ maybeFormatMessage,
+ newBlockIDs,
+ RateLimiter,
+ ScratchLinkWebsocket,
+ StringUtil,
+ taskQueue,
+ Timer,
+ AssetUtil,
+ StaticFetch,
+ uid,
+ VariableUtil,
+ xmlEscape
+};
module.exports = Util;
From 5cc38628d745aeae2658e16ab73befba2a1509f5 Mon Sep 17 00:00:00 2001
From: LilyMakesThings <127533508+LilyMakesThings@users.noreply.github.com>
Date: Sat, 27 Apr 2024 15:01:03 +0100
Subject: [PATCH 102/202] Add BLE and BT to vm
---
src/virtual-machine.js | 8 ++++++++
1 file changed, 8 insertions(+)
diff --git a/src/virtual-machine.js b/src/virtual-machine.js
index 28632017311..cfaf80efee6 100644
--- a/src/virtual-machine.js
+++ b/src/virtual-machine.js
@@ -21,6 +21,9 @@ const formatMessage = require('format-message');
const Variable = require('./engine/variable');
const newBlockIds = require('./util/new-block-ids');
+const BLE = require('./engine/ble');
+const BT = require('./io/bt');
+
const {loadCostume} = require('./import/load-costume.js');
const {loadSound} = require('./import/load-sound.js');
const {serializeSounds, serializeCostumes} = require('./serialization/serialize-assets');
@@ -71,6 +74,11 @@ class VirtualMachine extends EventEmitter {
log.error(`Failed to register runtime service: ${JSON.stringify(e)}`);
});
+ this.io = {
+ ble,
+ bt
+ }
+
/**
* The "currently editing"/selected target ID for the VM.
* Block events from any Blockly workspace are routed to this target.
From 9688278f1bddeafbe932c74f4c385d32f9274099 Mon Sep 17 00:00:00 2001
From: LilyMakesThings <127533508+LilyMakesThings@users.noreply.github.com>
Date: Sat, 27 Apr 2024 15:13:16 +0100
Subject: [PATCH 103/202] I might be an idiot
---
src/virtual-machine.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/virtual-machine.js b/src/virtual-machine.js
index cfaf80efee6..4709eeaa594 100644
--- a/src/virtual-machine.js
+++ b/src/virtual-machine.js
@@ -21,7 +21,7 @@ const formatMessage = require('format-message');
const Variable = require('./engine/variable');
const newBlockIds = require('./util/new-block-ids');
-const BLE = require('./engine/ble');
+const BLE = require('./io/ble');
const BT = require('./io/bt');
const {loadCostume} = require('./import/load-costume.js');
From af51643a79a60bf0b050992cfa40fab3ffe41b65 Mon Sep 17 00:00:00 2001
From: LilyMakesThings <127533508+LilyMakesThings@users.noreply.github.com>
Date: Sat, 27 Apr 2024 15:17:15 +0100
Subject: [PATCH 104/202] Update virtual-machine.js
---
src/virtual-machine.js | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/virtual-machine.js b/src/virtual-machine.js
index 4709eeaa594..6ad65f30eb1 100644
--- a/src/virtual-machine.js
+++ b/src/virtual-machine.js
@@ -75,8 +75,8 @@ class VirtualMachine extends EventEmitter {
});
this.io = {
- ble,
- bt
+ BLE,
+ BT
}
/**
From bdac43dcac4754bc0654a367c3207e14fcafe927 Mon Sep 17 00:00:00 2001
From: LilyMakesThings <127533508+LilyMakesThings@users.noreply.github.com>
Date: Tue, 30 Apr 2024 11:55:21 +0100
Subject: [PATCH 105/202] Update tw-scratchx-compatibility-layer.js (#40)
Co-authored-by: KyleKart <71104484+KyleKart@users.noreply.github.com>
---
src/extension-support/tw-scratchx-compatibility-layer.js | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/src/extension-support/tw-scratchx-compatibility-layer.js b/src/extension-support/tw-scratchx-compatibility-layer.js
index bceae527aae..7f4b8c72eb0 100644
--- a/src/extension-support/tw-scratchx-compatibility-layer.js
+++ b/src/extension-support/tw-scratchx-compatibility-layer.js
@@ -79,6 +79,11 @@ const parseScratchXArgument = (argument, defaultValue) => {
const split = argument.split(/\.|:/);
const menuName = split[1];
result.menu = menuName;
+ } else if (argument === 'b') {
+ result.type = ArgumentType.BOOLEAN;
+ if (!hasDefaultValue) {
+ result.defaultValue = '';
+ }
} else {
throw new Error(`Unknown ScratchX argument type: ${argument}`);
}
From e3708b6bc1456d5c520c4fdd55f0d9a2579d7e4b Mon Sep 17 00:00:00 2001
From: LilyMakesThings <127533508+LilyMakesThings@users.noreply.github.com>
Date: Thu, 2 May 2024 10:06:07 +0100
Subject: [PATCH 106/202] Update sb3.js (#41)
---
src/serialization/sb3.js | 2 ++
1 file changed, 2 insertions(+)
diff --git a/src/serialization/sb3.js b/src/serialization/sb3.js
index 14fb90d6ac2..1f6d51ab034 100644
--- a/src/serialization/sb3.js
+++ b/src/serialization/sb3.js
@@ -44,6 +44,7 @@ const INPUT_DIFF_BLOCK_SHADOW = 3; // obscured shadow
// Constants used during deserialization of an SB3 file
const CORE_EXTENSIONS = [
'argument',
+ 'camera',
'colour',
'control',
'data',
@@ -54,6 +55,7 @@ const CORE_EXTENSIONS = [
'operator',
'procedures',
'sensing',
+ 'string',
'sound'
];
From a827732cc1b72138322b0d3e20e493097abe68ed Mon Sep 17 00:00:00 2001
From: LilyMakesThings <127533508+LilyMakesThings@users.noreply.github.com>
Date: Fri, 17 May 2024 05:59:17 +0100
Subject: [PATCH 107/202] Reimplement MenuState (#43)
* Add menuState
* lint
---
src/blocks/scratch3_operators.js | 4 +--
src/blocks/scratch3_procedures.js | 1 +
src/blocks/scratch3_string.js | 39 +++++++++++-----------
src/compiler/compat-blocks.js | 4 +--
src/compiler/irgen.js | 14 ++++----
src/compiler/jsgen.js | 10 +++---
src/engine/camera.js | 16 ++++-----
src/engine/runtime.js | 18 +++++-----
src/engine/tw-interpolate.js | 2 +-
src/extension-support/argument-type.js | 2 +-
src/extension-support/block-type.js | 2 +-
src/extension-support/extension-manager.js | 25 +++++++++++---
src/serialization/sb3.js | 6 ++--
src/sprites/rendered-target.js | 2 +-
src/virtual-machine.js | 2 +-
15 files changed, 83 insertions(+), 64 deletions(-)
diff --git a/src/blocks/scratch3_operators.js b/src/blocks/scratch3_operators.js
index f710efb0355..db1bb5df1f1 100644
--- a/src/blocks/scratch3_operators.js
+++ b/src/blocks/scratch3_operators.js
@@ -115,9 +115,9 @@ class Scratch3OperatorsBlocks {
if (from > to) {
return Math.min(Math.max(n, to), from);
- } else {
- return Math.min(Math.max(n, from), to);
}
+ return Math.min(Math.max(n, from), to);
+
}
random (args) {
diff --git a/src/blocks/scratch3_procedures.js b/src/blocks/scratch3_procedures.js
index 1f55d32bf16..e8205b569d6 100644
--- a/src/blocks/scratch3_procedures.js
+++ b/src/blocks/scratch3_procedures.js
@@ -90,6 +90,7 @@ class Scratch3ProcedureBlocks {
}
util.startProcedure(procedureCode);
+ util.thread.tryCompile();
}
return (args, util) {
diff --git a/src/blocks/scratch3_string.js b/src/blocks/scratch3_string.js
index 59c70a3fb43..783da2218f7 100644
--- a/src/blocks/scratch3_string.js
+++ b/src/blocks/scratch3_string.js
@@ -44,7 +44,8 @@ class Scratch3StringBlocks {
reverse (args) { // usb
const str = Cast.toString(args.STRING);
- return str.split("").reverse().join("");
+ return str.split('').reverse()
+ .join('');
}
repeat (args) { // usb
@@ -59,7 +60,7 @@ class Scratch3StringBlocks {
const replacer = Cast.toString(args.WITH);
const str = Cast.toString(args.STRING);
- return str.replace(new RegExp(old, "gi"), replacer);
+ return str.replace(new RegExp(old, 'gi'), replacer);
}
letterOf (args) {
@@ -69,10 +70,10 @@ class Scratch3StringBlocks {
_getLetterOf (string, index) { // usb // used by compiler
// usb: we support some weird dropdowns now
- if (index === "last") {
+ if (index === 'last') {
index = string.length - 1;
- } else if (index === "random") {
- index = Math.floor(Math.random()*string.length);
+ } else if (index === 'random') {
+ index = Math.floor(Math.random() * string.length);
} else {
index = Cast.toNumber(index) - 1;
}
@@ -103,15 +104,15 @@ class Scratch3StringBlocks {
_getItemFromSplit (string, split, index) { // used by compiler
const splitString = string.split(split);
- if (index === "last") {
+ if (index === 'last') {
index = splitString.length - 1;
- } else if (index === "random") {
- index = Math.floor(Math.random()*splitString.length);
+ } else if (index === 'random') {
+ index = Math.floor(Math.random() * splitString.length);
} else {
index = Cast.toNumber(index) - 1;
}
- return splitString[index] ?? "";
+ return splitString[index] ?? '';
}
ternary (args) { // usb
@@ -130,11 +131,11 @@ class Scratch3StringBlocks {
}
_convertString (string, textCase) { // used by compiler
- if (textCase === "lowercase") {
+ if (textCase === 'lowercase') {
return string.toLowerCase();
- } else {
- return string.toUpperCase();
}
+ return string.toUpperCase();
+
}
indexOf (args) { // usb
@@ -148,17 +149,17 @@ class Scratch3StringBlocks {
const length = find.length;
if (length > string.length) return 0;
- let occurences = [];
+ const occurences = [];
for (let i = 0; i < string.length; i++) {
if (string.substring(i, i + length) === find) {
occurences.push(i);
}
}
- if (index === "last") {
+ if (index === 'last') {
index = occurences.length - 1;
- } else if (index === "random") {
- index = Math.floor(Math.random()*occurences.length);
+ } else if (index === 'random') {
+ index = Math.floor(Math.random() * occurences.length);
} else {
index = Cast.toNumber(index) - 1;
}
@@ -183,11 +184,11 @@ class Scratch3StringBlocks {
const str = Cast.toString(args.STRING);
const check = Cast.toString(args.CONVERT).toLowerCase();
- if (check === "lowercase") {
+ if (check === 'lowercase') {
return str.toLowerCase() === str;
- } else {
- return str.toUpperCase() === str;
}
+ return str.toUpperCase() === str;
+
}
}
diff --git a/src/compiler/compat-blocks.js b/src/compiler/compat-blocks.js
index 9fffe2f7fb4..fe6825e04f0 100644
--- a/src/compiler/compat-blocks.js
+++ b/src/compiler/compat-blocks.js
@@ -40,7 +40,7 @@ const stacked = [
'sound_playuntildone',
'sound_seteffectto',
'sound_setvolumeto',
- 'sound_stopallsounds',
+ 'sound_stopallsounds'
];
const inputs = [
@@ -56,7 +56,7 @@ const inputs = [
'string_item_split',
'string_convert',
'string_index_of',
- 'string_ternary',
+ 'string_ternary'
];
module.exports = {
diff --git a/src/compiler/irgen.js b/src/compiler/irgen.js
index 6dfd0698769..90ad0678881 100644
--- a/src/compiler/irgen.js
+++ b/src/compiler/irgen.js
@@ -391,12 +391,12 @@ class ScriptTreeGenerator {
kind: 'op.length',
string: this.descendInputOfBlock(block, 'STRING')
};
-// case 'operator_letter_of':
-// return {
-// kind: 'op.letterOf',
-// letter: this.descendInputOfBlock(block, 'LETTER'),
-// string: this.descendInputOfBlock(block, 'STRING')
-// };
+ // case 'operator_letter_of':
+ // return {
+ // kind: 'op.letterOf',
+ // letter: this.descendInputOfBlock(block, 'LETTER'),
+ // string: this.descendInputOfBlock(block, 'STRING')
+ // };
case 'operator_letters_of':
return {
kind: 'op.lettersOf',
@@ -794,7 +794,7 @@ class ScriptTreeGenerator {
kind: 'constant',
value: true
},
- code: this.descendSubstack(block, 'SUBSTACK'),
+ code: this.descendSubstack(block, 'SUBSTACK')
};
case 'control_clear_counter':
return {
diff --git a/src/compiler/jsgen.js b/src/compiler/jsgen.js
index fce7ca281f2..786eeb67363 100644
--- a/src/compiler/jsgen.js
+++ b/src/compiler/jsgen.js
@@ -609,8 +609,8 @@ class JSGenerator {
const right = this.descendInput(node.right);
return new TypedInput(`(${left.asUnknown()} <= ${right.asUnknown()})`, TYPE_BOOLEAN);
}
-// case 'op.letterOf':
-// return new TypedInput(`((${this.descendInput(node.string).asString()})[(${this.descendInput(node.letter).asNumber()} | 0) - 1] || "")`, TYPE_STRING);
+ // case 'op.letterOf':
+ // return new TypedInput(`((${this.descendInput(node.string).asString()})[(${this.descendInput(node.letter).asNumber()} | 0) - 1] || "")`, TYPE_STRING);
case 'op.lettersOf':
return new TypedInput(`((${this.descendInput(node.string).asString()}).substring(${this.descendInput(node.left).asNumber() - 1}, ${this.descendInput(node.right).asNumber()}) || "")`, TYPE_STRING);
case 'op.ln':
@@ -766,11 +766,11 @@ class JSGenerator {
return new TypedInput(`(${this.descendInput(node.left).asUnknown()} === ${this.descendInput(node.right).asUnknown()})`, TYPE_UNKNOWN);
case 'str.is': {
const str = this.descendInput(node.left).asString();
- if (node.right.toLowerCase() === "uppercase") {
+ if (node.right.toLowerCase() === 'uppercase') {
return new TypedInput(`${str.toUpperCase() === str}`, TYPE_BOOLEAN);
- } else {
- return new TypedInput(`${str.toLowerCase() === str}`, TYPE_BOOLEAN);
}
+ return new TypedInput(`${str.toLowerCase() === str}`, TYPE_BOOLEAN);
+
}
case 'str.repeat':
return new TypedInput(`(${this.descendInput(node.str).asString()}.repeat(${this.descendInput(node.num).asNumber()}))`, TYPE_STRING);
diff --git a/src/engine/camera.js b/src/engine/camera.js
index 3f35004cb6e..691104ff4f1 100644
--- a/src/engine/camera.js
+++ b/src/engine/camera.js
@@ -13,7 +13,7 @@ const MathUtil = require('../util/math-util');
* Camera: instance of a camera object on the stage.
*/
class Camera extends EventEmitter {
- constructor(runtime) {
+ constructor (runtime) {
super();
this.runtime = runtime;
@@ -67,7 +67,7 @@ class Camera extends EventEmitter {
* @param x The x coordinate.
* @param y The y coordinate.
*/
- setXY(x, y) {
+ setXY (x, y) {
this.x = Cast.toNumber(x);
this.y = Cast.toNumber(y);
@@ -78,7 +78,7 @@ class Camera extends EventEmitter {
* Set the zoom of the camera.
* @param zoom The new zoom value.
*/
- setZoom(zoom) {
+ setZoom (zoom) {
this.zoom = Cast.toNumber(zoom);
if (this.runtime.runtimeOptions.miscLimits) {
this.zoom = MathUtil.clamp(this.zoom, 10, 300);
@@ -91,7 +91,7 @@ class Camera extends EventEmitter {
* Point the camera towards a given direction.
* @param direction Direction to point the camera.
*/
- setDirection(direction) {
+ setDirection (direction) {
if (!isFinite(direction)) return;
this.direction = MathUtil.wrapClamp(direction, -179, 180);
@@ -103,14 +103,14 @@ class Camera extends EventEmitter {
* Set whether the camera will affect the projection.
* @param enabled The new enabled state.
*/
- setEnabled(enabled) {
+ setEnabled (enabled) {
this.enabled = enabled;
}
/**
* Tell the renderer to update the rendered camera state.
*/
- emitCameraUpdate() {
+ emitCameraUpdate () {
if (!this.runtime.renderer) return;
this.runtime.renderer._updateCamera(
@@ -118,7 +118,7 @@ class Camera extends EventEmitter {
this.y,
this.direction,
this.zoom
- )
+ );
this.runtime.requestRedraw();
}
@@ -126,7 +126,7 @@ class Camera extends EventEmitter {
/**
* Reset all camera properties.
*/
- reset() {
+ reset () {
this.x = 0;
this.y = 0;
this.direction = 90;
diff --git a/src/engine/runtime.js b/src/engine/runtime.js
index 3a5f3ec6ee2..d885461b724 100644
--- a/src/engine/runtime.js
+++ b/src/engine/runtime.js
@@ -152,16 +152,16 @@ const ArgumentTypeMap = (() => {
const FieldTypeMap = (() => {
const map = {};
map[ArgumentType.ANGLE] = {
- fieldName: "field_angle",
+ fieldName: 'field_angle'
};
map[ArgumentType.NUMBER] = {
- fieldName: "field_number",
+ fieldName: 'field_number'
};
map[ArgumentType.STRING] = {
- fieldName: "field_input",
+ fieldName: 'field_input'
};
map[ArgumentType.NOTE] = {
- fieldName: "field_note",
+ fieldName: 'field_note'
};
return map;
})();
@@ -1258,7 +1258,7 @@ class Runtime extends EventEmitter {
const acceptInput = (menuInfo.acceptText || menuInfo.acceptNumber);
const type = menuInfo.acceptText ?
'field_textdropdown' : menuInfo.acceptNumber ?
- 'field_numberdropdown' : 'field_dropdown';
+ 'field_numberdropdown' : 'field_dropdown';
return {
json: {
message0: '%1',
@@ -1669,7 +1669,7 @@ class Runtime extends EventEmitter {
return {
type: 'field_label_serializable',
name: placeholder,
- text: argInfo.defaultValue,
+ text: argInfo.defaultValue
};
}
@@ -1736,9 +1736,9 @@ class Runtime extends EventEmitter {
const menuInfo = context.categoryInfo.menuInfo[argInfo.menu];
let acceptReporters = false;
- if (typeof argInfo.acceptReporters !== "undefined") {
+ if (typeof argInfo.acceptReporters !== 'undefined') {
acceptReporters = argInfo.acceptReporters;
- } else if (typeof menuInfo.acceptReporters !== "undefined") {
+ } else if (typeof menuInfo.acceptReporters !== 'undefined') {
acceptReporters = menuInfo.acceptReporters;
}
@@ -2844,7 +2844,7 @@ class Runtime extends EventEmitter {
/**
* Get the current instance of the camera.
*/
- getCamera() {
+ getCamera () {
return this.camera;
}
diff --git a/src/engine/tw-interpolate.js b/src/engine/tw-interpolate.js
index bf63f2d079b..deb06497c47 100644
--- a/src/engine/tw-interpolate.js
+++ b/src/engine/tw-interpolate.js
@@ -23,7 +23,7 @@ const setupInitialState = runtime => {
y: camera.y,
direction: camera.direction,
zoom: camera.zoom
- }
+ };
} else {
camera.interpolationData = null;
}
diff --git a/src/extension-support/argument-type.js b/src/extension-support/argument-type.js
index f8cf31a4e3a..eb97555c9b9 100644
--- a/src/extension-support/argument-type.js
+++ b/src/extension-support/argument-type.js
@@ -76,7 +76,7 @@ const ArgumentType = {
/**
* Name of variable in the current specified target(s)
*/
- VARIABLE: 'variable',
+ VARIABLE: 'variable'
};
module.exports = ArgumentType;
diff --git a/src/extension-support/block-type.js b/src/extension-support/block-type.js
index 8c24b04dc9f..4800291cc80 100644
--- a/src/extension-support/block-type.js
+++ b/src/extension-support/block-type.js
@@ -70,7 +70,7 @@ const BlockType = {
/**
* Arbitrary scratch-blocks XML.
*/
- XML: 'xml',
+ XML: 'xml'
};
module.exports = BlockType;
diff --git a/src/extension-support/extension-manager.js b/src/extension-support/extension-manager.js
index 0f489120022..2ac61f8c588 100644
--- a/src/extension-support/extension-manager.js
+++ b/src/extension-support/extension-manager.js
@@ -430,6 +430,18 @@ class ExtensionManager {
*/
_prepareMenuInfo (serviceName, menus) {
const menuNames = Object.getOwnPropertyNames(menus);
+
+ // `makeGetItemsShim` is like `bind` except that it explicitly doesn't bind `this`
+ const makeGetItemsShim = (serviceObject, menuItemFunctionName) => {
+ const extensionManager = this;
+ return function () {
+ // scratch-blocks passes the menu as `this` when calling a menu's `items` function
+ const scratchBlocksMenuObject = this; // eslint-disable-line no-invalid-this
+ return extensionManager._getExtensionMenuItems(
+ serviceObject, menuItemFunctionName, scratchBlocksMenuObject);
+ };
+ };
+
for (let i = 0; i < menuNames.length; i++) {
const menuName = menuNames[i];
let menuInfo = menus[menuName];
@@ -447,8 +459,7 @@ class ExtensionManager {
if (typeof menuInfo.items === 'string') {
const menuItemFunctionName = menuInfo.items;
const serviceObject = dispatch.services[serviceName];
- // Bind the function here so we can pass a simple item generation function to Scratch Blocks later.
- menuInfo.items = this._getExtensionMenuItems.bind(this, serviceObject, menuItemFunctionName);
+ menuInfo.items = makeGetItemsShim(serviceObject, menuItemFunctionName);
}
}
return menus;
@@ -458,19 +469,25 @@ class ExtensionManager {
* Fetch the items for a particular extension menu, providing the target ID for context.
* @param {object} extensionObject - the extension object providing the menu.
* @param {string} menuItemFunctionName - the name of the menu function to call.
+ * @param {object} scratchBlocksMenuObject - the scratch-blocks menu object, for access to its current state.
* @returns {Array} menu items ready for scratch-blocks.
* @private
*/
- _getExtensionMenuItems (extensionObject, menuItemFunctionName) {
+ _getExtensionMenuItems (extensionObject, menuItemFunctionName, scratchBlocksMenuObject) {
// Fetch the items appropriate for the target currently being edited. This assumes that menus only
// collect items when opened by the user while editing a particular target.
const editingTarget = this.runtime.getEditingTarget() || this.runtime.getTargetForStage();
const editingTargetID = editingTarget ? editingTarget.id : null;
const extensionMessageContext = this.runtime.makeMessageContextForTarget(editingTarget);
+ // trim the menu state down to structured-copy-compatible properties that extensions might need
+ const menuState = {
+ selectedValue: scratchBlocksMenuObject.getValue()
+ };
+
// TODO: Fix this to use dispatch.call when extensions are running in workers.
const menuFunc = extensionObject[menuItemFunctionName];
- const menuItems = menuFunc.call(extensionObject, editingTargetID).map(
+ const menuItems = menuFunc.call(extensionObject, editingTargetID, menuState).map(
item => {
item = maybeFormatMessage(item, extensionMessageContext);
switch (typeof item) {
diff --git a/src/serialization/sb3.js b/src/serialization/sb3.js
index 1f6d51ab034..a8d3381bb9b 100644
--- a/src/serialization/sb3.js
+++ b/src/serialization/sb3.js
@@ -794,9 +794,9 @@ const serialize = function (runtime, targetId, {allowOptimization = true} = {})
// USB: A few mods have agreed to list our platform's name inside of the project json.
// We also add a couple more bits specific to Unsandboxed.
const platformMeta = Object.create(null);
- platformMeta.name = "Unsandboxed";
- platformMeta.url = "https://unsandboxed.org/";
- platformMeta.version = "alpha";
+ platformMeta.name = 'Unsandboxed';
+ platformMeta.url = 'https://unsandboxed.org/';
+ platformMeta.version = 'alpha';
meta.platform = platformMeta;
// Attach full user agent string to metadata if available
diff --git a/src/sprites/rendered-target.js b/src/sprites/rendered-target.js
index 6aaccc249f3..680d6bab64c 100644
--- a/src/sprites/rendered-target.js
+++ b/src/sprites/rendered-target.js
@@ -233,7 +233,7 @@ class RenderedTarget extends Target {
* @type {string}
*/
static get ROTATION_STYLE_LOOKING () {
- return "looking";
+ return 'looking';
}
/**
diff --git a/src/virtual-machine.js b/src/virtual-machine.js
index 6ad65f30eb1..ef280524793 100644
--- a/src/virtual-machine.js
+++ b/src/virtual-machine.js
@@ -77,7 +77,7 @@ class VirtualMachine extends EventEmitter {
this.io = {
BLE,
BT
- }
+ };
/**
* The "currently editing"/selected target ID for the VM.
From ecc638e4bed52e0a04bdfa71b8049aa1d9b0ee6a Mon Sep 17 00:00:00 2001
From: LilyMakesThings <127533508+LilyMakesThings@users.noreply.github.com>
Date: Fri, 17 May 2024 06:08:40 +0100
Subject: [PATCH 108/202] Update extension-manager.js (#44)
---
src/extension-support/extension-manager.js | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/src/extension-support/extension-manager.js b/src/extension-support/extension-manager.js
index 2ac61f8c588..e3370ac1c9a 100644
--- a/src/extension-support/extension-manager.js
+++ b/src/extension-support/extension-manager.js
@@ -482,7 +482,9 @@ class ExtensionManager {
// trim the menu state down to structured-copy-compatible properties that extensions might need
const menuState = {
- selectedValue: scratchBlocksMenuObject.getValue()
+ selectedValue: scratchBlocksMenuObject.getValue(),
+ sourceBlock: scratchBlocksMenuObject.sourceBlock_,
+ menuObject: scratchBlocksMenuObject,
};
// TODO: Fix this to use dispatch.call when extensions are running in workers.
From 5fd6aec30cef4ce561af2b4c61995892d62f669a Mon Sep 17 00:00:00 2001
From: LilyMakesThings <127533508+LilyMakesThings@users.noreply.github.com>
Date: Fri, 17 May 2024 16:01:36 +0100
Subject: [PATCH 109/202] API for custom context menus (#45)
* Create context-menu-context.js
* Update extension-manager.js
* Update tw-extension-api-common.js
* Update extension-manager.js
---
src/extension-support/context-menu-context.js | 25 +++++++++++++++++++
src/extension-support/extension-manager.js | 23 +++++++++++++++++
.../tw-extension-api-common.js | 2 ++
3 files changed, 50 insertions(+)
create mode 100644 src/extension-support/context-menu-context.js
diff --git a/src/extension-support/context-menu-context.js b/src/extension-support/context-menu-context.js
new file mode 100644
index 00000000000..5ad2e9cf0b3
--- /dev/null
+++ b/src/extension-support/context-menu-context.js
@@ -0,0 +1,25 @@
+/**
+ * Block argument types
+ * @enum {string}
+ */
+const ContextMenuContext = {
+ /**
+ * Indicates that the context menu item should appear regardless of whether
+ * the block is in the toolbox or on the main workspace.
+ */
+ ALL: 'all',
+
+ /**
+ * Indicates that the context menu item should only appear on a block
+ * if it is in the toolbox.
+ */
+ TOOLBOX_ONLY: 'toolbox',
+
+ /**
+ * Indicates that the context menu item should only appear on a block
+ * if it is on the main workspace.
+ */
+ WORKSPACE_ONLY: 'workspace'
+};
+
+module.exports = ContextMenuContext;
diff --git a/src/extension-support/extension-manager.js b/src/extension-support/extension-manager.js
index e3370ac1c9a..754b004e9e9 100644
--- a/src/extension-support/extension-manager.js
+++ b/src/extension-support/extension-manager.js
@@ -382,6 +382,16 @@ class ExtensionManager {
});
}
+ /**
+ * Modify the provided text as necessary to ensure that it may be used as an attribute value in valid XML.
+ * @param {string} text - the text to be sanitized
+ * @returns {string} - the sanitized text
+ * @private
+ */
+ _sanitizeID (text) {
+ return text.toString().replace(/[<"&]/, '_');
+ }
+
/**
* Apply minor cleanup and defaults for optional extension fields.
* TODO: make the ID unique in cases where two copies of the same extension are loaded.
@@ -534,6 +544,19 @@ class ExtensionManager {
}, blockInfo);
blockInfo.text = blockInfo.text || blockInfo.opcode;
+ if (blockInfo.customContextMenu && blockInfo.customContextMenu.length > 0) {
+ // Replace all the string callback names of the context menu items
+ // with the actual function call.
+ blockInfo.customContextMenu = blockInfo.customContextMenu.map(contextMenuOption => {
+ if (typeof contextMenuOption.callback === 'string') {
+ const callbackName = this._sanitizeID(contextMenuOption.callback);
+ contextMenuOption.callback = args =>
+ dispatch.call(serviceName, callbackName, args);
+ }
+ return contextMenuOption;
+ });
+ }
+
switch (blockInfo.blockType) {
case BlockType.EVENT:
if (blockInfo.func) {
diff --git a/src/extension-support/tw-extension-api-common.js b/src/extension-support/tw-extension-api-common.js
index dcc782ff407..a0e49dc3f93 100644
--- a/src/extension-support/tw-extension-api-common.js
+++ b/src/extension-support/tw-extension-api-common.js
@@ -1,6 +1,7 @@
const ArgumentType = require('./argument-type');
const BlockType = require('./block-type');
const TargetType = require('./target-type');
+const ContextMenuContext = require('./context-menu-context');
const Cast = require('../util/cast');
const Util = require('../util/usb-util');
@@ -8,6 +9,7 @@ const Scratch = {
ArgumentType,
BlockType,
TargetType,
+ ContextMenuContext,
Cast,
Util
};
From abf565df01b494b0e8a66af6341730207ad29bde Mon Sep 17 00:00:00 2001
From: LilyMakesThings
Date: Sun, 19 May 2024 13:41:31 +0100
Subject: [PATCH 110/202] pending monitors
---
src/engine/blocks.js | 103 +++++++++++++++---
src/engine/runtime.js | 121 ++++++++++++++++++++-
src/engine/target.js | 17 +++
src/extension-support/extension-manager.js | 2 +-
src/util/string-util.js | 15 +++
src/virtual-machine.js | 3 +
6 files changed, 242 insertions(+), 19 deletions(-)
diff --git a/src/engine/blocks.js b/src/engine/blocks.js
index bb397832cae..e5b9410a113 100644
--- a/src/engine/blocks.js
+++ b/src/engine/blocks.js
@@ -477,6 +477,12 @@ class Blocks {
if (e.isLocal && editingTarget && !editingTarget.isStage && !e.isCloud) {
if (!editingTarget.lookupVariableById(e.varId)) {
editingTarget.createVariable(e.varId, e.varName, e.varType);
+
+ this.runtime.addPendingMonitor(e.varId);
+
+ // TODO this should probably be batched
+ // (esp. if we receive multiple new var_creates in a row).
+ this.runtime.requestToolboxExtensionsUpdate();
this.emitProjectChanged();
}
} else {
@@ -492,6 +498,12 @@ class Blocks {
}
}
stage.createVariable(e.varId, e.varName, e.varType, e.isCloud);
+
+ this.runtime.addPendingMonitor(e.varId);
+
+ // TODO same as above, this should probably be batched
+ // (esp. if we receive multiple new var_creates in a row).
+ this.runtime.requestToolboxExtensionsUpdate();
this.emitProjectChanged();
}
break;
@@ -655,6 +667,20 @@ class Blocks {
this._addScript(block.id);
}
+ // A block was just created, see if it had an associated pending monitor,
+ // if so, keep track of the monitor state change by mimicing the checkbox
+ // event from the flyout. Clear record of this block from
+ // the pending monitors list.
+ if (this === this.runtime.monitorBlocks &&
+ this.runtime.getPendingMonitor(block.id)) {
+ this.changeBlock({
+ id: block.id, // Monitor blocks for variables are the variable ID.
+ element: 'checkbox', // Mimic checkbox event from flyout.
+ value: true
+ }, this.runtime);
+ this.runtime.removePendingMonitor(block.id);
+ }
+
this.resetCache();
// A new block was actually added to the block container,
@@ -937,6 +963,41 @@ class Blocks {
this.emitProjectChanged();
}
+ getAllReferencesForVariable (variable) {
+ let fieldName;
+ let truncatedOpcode;
+ if (variable.type === Variable.SCALAR_TYPE) {
+ fieldName = 'VARIABLE';
+ truncatedOpcode = 'variable';
+ } else if (variable.type === Variable.LIST_TYPE) {
+ fieldName = 'LIST';
+ truncatedOpcode = 'listcontents';
+ } else {
+ // TODO handle broadcast messages later
+ return [];
+ }
+
+ const variableBlocks = [];
+ for (const blockId in this._blocks) {
+ if (!this._blocks.hasOwnProperty(blockId)) continue;
+ const block = this._blocks[blockId];
+ // Check for blocks with fields referencing variable/list, otherwise variable/list reporters
+ if (block.fields[fieldName] &&
+ block.fields[fieldName].value === variable.name) {
+ // It's a block containing a variable field whose currently selected value
+ // matches the given variable name
+ variableBlocks.push(block);
+ } else if (block.mutation &&
+ block.mutation.blockInfo &&
+ block.mutation.blockInfo.opcode === truncatedOpcode &&
+ block.mutation.blockInfo.text === variable.name) {
+ // It's a variable reporter whose name matches the given variable
+ variableBlocks.push(block);
+ }
+ }
+ return variableBlocks;
+ }
+
/**
* Delete all blocks and their associated scripts.
*/
@@ -1222,25 +1283,33 @@ class Blocks {
xmlString += '';
}
}
- // Add any fields on this block.
- for (const field in block.fields) {
- if (!Object.prototype.hasOwnProperty.call(block.fields, field)) continue;
- const blockField = block.fields[field];
- xmlString += `${value}`;
}
- xmlString += `>${value}`;
}
+
// Add blocks connected to the next connection.
if (block.next) {
xmlString += `${this.blockToXML(block.next, comments)}`;
diff --git a/src/engine/runtime.js b/src/engine/runtime.js
index d885461b724..e3e176a4929 100644
--- a/src/engine/runtime.js
+++ b/src/engine/runtime.js
@@ -23,6 +23,8 @@ const ScratchLinkWebSocket = require('../util/scratch-link-websocket');
const FontManager = require('./tw-font-manager');
const fetchWithTimeout = require('../util/fetch-with-timeout');
+const CORE_BLOCKS = [];
+
// Virtual I/O devices.
const Clock = require('../io/clock');
const Cloud = require('../io/cloud');
@@ -367,6 +369,12 @@ class Runtime extends EventEmitter {
*/
this._prevMonitorState = OrderedMap({});
+ /**
+ * Track any monitors to be added that don't have corresponding monitor blocks
+ * created yet.
+ */
+ this._pendingMonitors = new Set();
+
/**
* Whether the project is in "turbo mode."
* @type {Boolean}
@@ -922,6 +930,10 @@ class Runtime extends EventEmitter {
return 'BLOCKSINFO_UPDATE';
}
+ static get BLOCK_UPDATE () {
+ return 'BLOCK_UPDATE';
+ }
+
/**
* Event name when the runtime tick loop has been started.
* @const {string}
@@ -1146,6 +1158,7 @@ class Runtime extends EventEmitter {
categoryInfo.customFieldTypes = {};
categoryInfo.menus = [];
categoryInfo.menuInfo = {};
+ categoryInfo.convertedMenuInfo = {};
for (const menuName in extensionInfo.menus) {
if (Object.prototype.hasOwnProperty.call(extensionInfo.menus, menuName)) {
@@ -1153,6 +1166,13 @@ class Runtime extends EventEmitter {
const convertedMenu = this._buildMenuForScratchBlocks(menuName, menuInfo, categoryInfo);
categoryInfo.menus.push(convertedMenu);
categoryInfo.menuInfo[menuName] = menuInfo;
+ // Use the convertedMenu and `menuInfo` to consolidate
+ // the information needed to layout the menu correctly
+ // on a dynamic extension block.
+ categoryInfo.convertedMenuInfo[menuName] = {
+ items: Array.isArray(menuInfo.items) ? convertedMenu.json.args0[0].options : menuInfo.items,
+ acceptReporters: menuInfo.acceptReporters || false
+ };
}
}
for (const fieldTypeName in extensionInfo.customFieldTypes) {
@@ -1532,7 +1552,9 @@ class Runtime extends EventEmitter {
const mutation = blockInfo.isDynamic ? `` : '';
const inputs = context.inputList.join('');
- const blockXML = `${mutation}${inputs}`;
+ const toolboxIdXml = blockInfo.isDynamic && blockInfo.paletteKey ?
+ ` id="${xmlEscape(blockInfo.paletteKey)}"` : '';
+ const blockXML = `${mutation}${inputs}`;
if (blockInfo.extensions) {
for (const extension of blockInfo.extensions) {
@@ -2393,6 +2415,7 @@ class Runtime extends EventEmitter {
const emptyMonitorState = OrderedMap({});
if (!emptyMonitorState.equals(this._monitorState)) {
this._monitorState = emptyMonitorState;
+ this._pendingMonitors.clear();
this.emit(Runtime.MONITORS_UPDATE, this._monitorState);
}
this.emit(Runtime.RUNTIME_DISPOSED);
@@ -3410,6 +3433,10 @@ class Runtime extends EventEmitter {
return varNames;
}
+ isCoreExtension (categoryId) {
+ return CORE_BLOCKS.indexOf(categoryId) > -1;
+ }
+
/**
* Get the label or label function for an opcode
* @param {string} extendedOpcode - the opcode you want a label for
@@ -3417,6 +3444,7 @@ class Runtime extends EventEmitter {
* @property {string} category - the category for this opcode
* @property {Function} [labelFn] - function to generate the label for this opcode
* @property {string} [label] - the label for this opcode if `labelFn` is absent
+ * @deprecated
*/
getLabelForOpcode (extendedOpcode) {
const [category, opcode] = StringUtil.splitFirst(extendedOpcode, '_');
@@ -3435,6 +3463,88 @@ class Runtime extends EventEmitter {
};
}
+ /**
+ * Get the monitor label and color for a given toolbox block id.
+ * @param {string} id - the block id for a block with a monitor being requested
+ * @return {object} - object with label and color
+ * @property {string} color - the color for the monitor for the given block id
+ * @property {string} label - the label for the monitor for the given block id
+ */
+ getMonitorLabelForBlock (id) {
+ // Having dynamic extension blocks makes it so that a single extension category can have
+ // multiple toolbox blocks with the same opcode. This previously wasn't possible.
+ // This means that we can no longer use just the opcode to look up the block and
+ // should instead look up the block using its unique id.
+ const block = this.flyoutBlocks.getBlock(id);
+ // If the block doesn't actually exist in the flyout, we should not
+ // be creating a label for it.
+ if (!block) return;
+
+ // Look up category info from the block
+ const extendedOpcode = block.opcode;
+ const [category, opcode] = StringUtil.splitFirst(extendedOpcode, '_');
+ if (!(category && opcode)) return;
+
+ const categoryInfo = this._blockInfo.find(ci => ci.id === category);
+ if (!categoryInfo) return;
+
+ const isDynamicExtensionBlock =
+ block.mutation &&
+ block.mutation.blockInfo &&
+ block.mutation.blockInfo.isDynamic;
+ let extensionBlockInfo;
+ if (isDynamicExtensionBlock) {
+ extensionBlockInfo = block.mutation.blockInfo;
+ } else {
+ const extensionBlockDesc = categoryInfo.blocks.find(b => b.info.opcode === opcode);
+ extensionBlockInfo = (extensionBlockDesc && extensionBlockDesc.info) || null;
+ }
+ if (!extensionBlockInfo) return;
+
+ // TODO get rid of this when we update the core extension name
+ const temporaryCategory = category === 'data2' ? 'data' : category;
+ if (this.isCoreExtension(temporaryCategory)) {
+ return {
+ color: extensionBlockInfo.color1 || categoryInfo.color1,
+ label: extensionBlockInfo.text
+ };
+ }
+
+ // TODO: we may want to format the label in a locale-specific way.
+ return {
+ category: 'extension', // This assumes that all extensions have the same monitor color.
+ label: `${categoryInfo.name}: ${extensionBlockInfo.text}`
+ };
+ }
+
+ /**
+ * Keep track of the fact that a monitor has been requested
+ * for the given blockId, even though the block with the given id
+ * does not exist in the flyout yet.
+ * @param {string} blockId The id of the block with a pending monitor
+ */
+ addPendingMonitor (blockId) {
+ this._pendingMonitors.add(blockId);
+ }
+
+ /**
+ * Removes a block id from the pending monitors list (e.g. if it has
+ * been turned into an actual monitor).
+ * @param {string} blockId The id of the block with a pending monitor
+ */
+ removePendingMonitor (blockId) {
+ this._pendingMonitors.delete(blockId);
+ }
+
+ /**
+ * Checks whether the given block id has a pending monitor.
+ * @param {string} blockId The id of the block with a pending monitor
+ * @return {boolean} True if the block has a pending monitor, false otherwise.
+ */
+ getPendingMonitor (blockId) {
+ return this._pendingMonitors.has(blockId);
+ }
+
/**
* Create a new global variable avoiding conflicts with other variable names.
* @param {string} variableName The desired variable name for the new global variable.
@@ -3479,6 +3589,15 @@ class Runtime extends EventEmitter {
this.emit(Runtime.BLOCKS_NEED_UPDATE);
}
+ /**
+ * Emit an event that indicates that the given block got updated.
+ * @param {string} blockId The id of the block
+ * @param {ExtensionBlockMetadata} blockInfo The new block info
+ */
+ updateBlockInfo (blockId, blockInfo) {
+ this.emit(Runtime.BLOCK_UPDATE, blockId, blockInfo);
+ }
+
/**
* Emit an event that indicates that the toolbox extension blocks need updating.
*/
diff --git a/src/engine/target.js b/src/engine/target.js
index e9cecfe0b0d..3f74128c619 100644
--- a/src/engine/target.js
+++ b/src/engine/target.js
@@ -137,6 +137,23 @@ class Target extends EventEmitter {
return newVariable;
}
+ /**
+ * Look up a variable object by name.
+ * Create a new variable with a generated ID if lookup fails.
+ * @param {string} name Name of the variable.
+ * @param {string} [type] Optional type of the variable (default: scalar).
+ * @return {!Variable} Variable object.
+ */
+ lookupOrCreateVariableByNameAndType (name, type = Variable.SCALAR_TYPE) {
+ let variable = this.lookupVariableByNameAndType(name, type);
+ if (!variable) {
+ // No variable with this name exists - create it locally.
+ variable = new Variable(uid(), name, Variable.SCALAR_TYPE, false);
+ this.variables[variable.id] = variable;
+ }
+ return variable;
+ }
+
/**
* Look up a broadcast message object with the given id and return it
* if it exists.
diff --git a/src/extension-support/extension-manager.js b/src/extension-support/extension-manager.js
index 754b004e9e9..0b97c625411 100644
--- a/src/extension-support/extension-manager.js
+++ b/src/extension-support/extension-manager.js
@@ -494,7 +494,7 @@ class ExtensionManager {
const menuState = {
selectedValue: scratchBlocksMenuObject.getValue(),
sourceBlock: scratchBlocksMenuObject.sourceBlock_,
- menuObject: scratchBlocksMenuObject,
+ state: scratchBlocksMenuObject,
};
// TODO: Fix this to use dispatch.call when extensions are running in workers.
diff --git a/src/util/string-util.js b/src/util/string-util.js
index 6fc99530f96..a691531adea 100644
--- a/src/util/string-util.js
+++ b/src/util/string-util.js
@@ -89,6 +89,21 @@ class StringUtil {
}
});
}
+
+ // Port over the string comparison utility from scratch-blocks
+ // ScratchBlocks.scratchBlocksUtils.compareStrings
+ /**
+ * Compare strings with natural number sorting.
+ * @param {string} str1 First input.
+ * @param {string} str2 Second input.
+ * @return {number} -1, 0, or 1 to signify greater than, equality, or less than.
+ */
+ static compareStrings (str1, str2) {
+ return str1.localeCompare(str2, [], {
+ sensitivity: 'base',
+ numeric: true
+ });
+ }
}
module.exports = StringUtil;
diff --git a/src/virtual-machine.js b/src/virtual-machine.js
index ef280524793..9380755815a 100644
--- a/src/virtual-machine.js
+++ b/src/virtual-machine.js
@@ -144,6 +144,9 @@ class VirtualMachine extends EventEmitter {
this.runtime.on(Runtime.BLOCKS_NEED_UPDATE, () => {
this.emitWorkspaceUpdate();
});
+ this.runtime.on(Runtime.BLOCK_UPDATE, (blockId, blockInfo) => {
+ this.emit(Runtime.BLOCK_UPDATE, blockId, blockInfo);
+ });
this.runtime.on(Runtime.TOOLBOX_EXTENSIONS_NEED_UPDATE, () => {
this.extensionManager.refreshBlocks();
});
From 76f22f09626e8003a60a4fcff4a409b40057f0b5 Mon Sep 17 00:00:00 2001
From: LilyMakesThings <127533508+LilyMakesThings@users.noreply.github.com>
Date: Mon, 27 May 2024 05:30:27 +0100
Subject: [PATCH 111/202] Add option to lock lists (#47)
* list locking
* Update variable.js
* allow for locks to be serialized
* Update sb3.js
---
src/blocks/scratch3_data.js | 6 ++++++
src/compiler/jsgen.js | 25 ++++++++++++++++---------
src/engine/variable.js | 6 ++++--
src/serialization/sb3.js | 12 ++++++++++--
4 files changed, 36 insertions(+), 13 deletions(-)
diff --git a/src/blocks/scratch3_data.js b/src/blocks/scratch3_data.js
index 1a2e5b86eea..006e2f09bea 100644
--- a/src/blocks/scratch3_data.js
+++ b/src/blocks/scratch3_data.js
@@ -126,6 +126,8 @@ class Scratch3DataBlocks {
addToList (args, util) {
const list = util.target.lookupOrCreateList(
args.LIST.id, args.LIST.name);
+ if (list.locked) return;
+
list.value.push(args.ITEM);
list._monitorUpToDate = false;
}
@@ -133,6 +135,7 @@ class Scratch3DataBlocks {
deleteOfList (args, util) {
const list = util.target.lookupOrCreateList(
args.LIST.id, args.LIST.name);
+ if (list.locked) return;
const index = Cast.toListIndex(args.INDEX, list.value.length, true);
if (index === Cast.LIST_INVALID) {
return;
@@ -147,6 +150,7 @@ class Scratch3DataBlocks {
deleteAllOfList (args, util) {
const list = util.target.lookupOrCreateList(
args.LIST.id, args.LIST.name);
+ if (list.locked) return;
list.value = [];
return;
}
@@ -155,6 +159,7 @@ class Scratch3DataBlocks {
const item = args.ITEM;
const list = util.target.lookupOrCreateList(
args.LIST.id, args.LIST.name);
+ if (list.locked) return;
const index = Cast.toListIndex(args.INDEX, list.value.length + 1, false);
if (index === Cast.LIST_INVALID) {
return;
@@ -167,6 +172,7 @@ class Scratch3DataBlocks {
const item = args.ITEM;
const list = util.target.lookupOrCreateList(
args.LIST.id, args.LIST.name);
+ if (list.locked) return;
const index = Cast.toListIndex(args.INDEX, list.value.length, false);
if (index === Cast.LIST_INVALID) {
return;
diff --git a/src/compiler/jsgen.js b/src/compiler/jsgen.js
index fc5e5ab0bb2..9faaae31d48 100644
--- a/src/compiler/jsgen.js
+++ b/src/compiler/jsgen.js
@@ -973,9 +973,12 @@ class JSGenerator {
this.yielded();
break;
+ // TODO: Lists that are locked don't need their monitors updated.
+ // In fact, they don't need to be executed at all, since right
+ // now, locked is a static value that cannot be changed.
case 'list.add': {
const list = this.referenceVariable(node.list);
- this.source += `${list}.value.push(${this.descendInput(node.item).asSafe()});\n`;
+ this.source += `if (!${list}.locked) ${list}.value.push(${this.descendInput(node.item).asSafe()});\n`;
this.source += `${list}._monitorUpToDate = false;\n`;
break;
}
@@ -984,12 +987,12 @@ class JSGenerator {
const index = this.descendInput(node.index);
if (index instanceof ConstantInput) {
if (index.constantValue === 'last') {
- this.source += `${list}.value.pop();\n`;
+ this.source += `if (!${list}.locked) ${list}.value.pop();\n`;
this.source += `${list}._monitorUpToDate = false;\n`;
break;
}
if (+index.constantValue === 1) {
- this.source += `${list}.value.shift();\n`;
+ this.source += `if (!${list}.locked) ${list}.value.shift();\n`;
this.source += `${list}._monitorUpToDate = false;\n`;
break;
}
@@ -998,9 +1001,11 @@ class JSGenerator {
this.source += `listDelete(${list}, ${index.asUnknown()});\n`;
break;
}
- case 'list.deleteAll':
- this.source += `${this.referenceVariable(node.list)}.value = [];\n`;
+ case 'list.deleteAll': {
+ const list = this.referenceVariable(node.list);
+ this.source += `if (!${list}.locked) ${list}.value = [];\n`;
break;
+ }
case 'list.hide':
this.source += `runtime.monitorBlocks.changeBlock({ id: "${sanitize(node.list.id)}", element: "checkbox", value: false }, runtime);\n`;
break;
@@ -1009,16 +1014,18 @@ class JSGenerator {
const index = this.descendInput(node.index);
const item = this.descendInput(node.item);
if (index instanceof ConstantInput && +index.constantValue === 1) {
- this.source += `${list}.value.unshift(${item.asSafe()});\n`;
+ this.source += `if (!${list}.locked) ${list}.value.unshift(${item.asSafe()});\n`;
this.source += `${list}._monitorUpToDate = false;\n`;
break;
}
- this.source += `listInsert(${list}, ${index.asUnknown()}, ${item.asSafe()});\n`;
+ this.source += `if (!${list}.locked) listInsert(${list}, ${index.asUnknown()}, ${item.asSafe()});\n`;
break;
}
- case 'list.replace':
- this.source += `listReplace(${this.referenceVariable(node.list)}, ${this.descendInput(node.index).asUnknown()}, ${this.descendInput(node.item).asSafe()});\n`;
+ case 'list.replace': {
+ const list = this.referenceVariable(node.list);
+ this.source += `if (!${list}.locked) listReplace(${list}, ${this.descendInput(node.index).asUnknown()}, ${this.descendInput(node.item).asSafe()});\n`;
break;
+ }
case 'list.show':
this.source += `runtime.monitorBlocks.changeBlock({ id: "${sanitize(node.list.id)}", element: "checkbox", value: true }, runtime);\n`;
break;
diff --git a/src/engine/variable.js b/src/engine/variable.js
index 2cf45b50379..50b2e347c28 100644
--- a/src/engine/variable.js
+++ b/src/engine/variable.js
@@ -14,11 +14,12 @@ class Variable {
* @param {boolean} isCloud Whether the variable is stored in the cloud.
* @constructor
*/
- constructor (id, name, type, isCloud) {
+ constructor (id, name, type, isCloud, locked) {
this.id = id || uid();
this.name = name;
this.type = type;
this.isCloud = isCloud;
+ this.locked = !!locked;
switch (this.type) {
case Variable.SCALAR_TYPE:
this.value = 0;
@@ -37,7 +38,8 @@ class Variable {
toXML (isLocal) {
isLocal = (isLocal === true);
return `${xmlEscape(this.name)}`;
+ }" iscloud="${this.isCloud}" islocked="${this.locked
+ }">${xmlEscape(this.name)}`;
}
/**
diff --git a/src/serialization/sb3.js b/src/serialization/sb3.js
index 97424915c93..0dcdec568af 100644
--- a/src/serialization/sb3.js
+++ b/src/serialization/sb3.js
@@ -536,7 +536,12 @@ const serializeVariables = function (variables) {
continue;
}
if (v.type === Variable.LIST_TYPE) {
- obj.lists[varId] = [v.name, makeSafeForJSON(v.value)];
+ obj.lists[varId] = [
+ v.name,
+ makeSafeForJSON(v.value),
+ false,
+ v.locked
+ ];
continue;
}
@@ -691,6 +696,7 @@ const serializeMonitors = function (monitors, runtime, extensions) {
value: Array.isArray(monitorData.value) ? [] : 0,
width: monitorData.width,
height: monitorData.height,
+ locked: monitorData.locked,
x: monitorData.x - xOffset,
y: monitorData.y - yOffset,
visible: monitorData.visible
@@ -1249,7 +1255,8 @@ const parseScratchObject = function (object, runtime, extensions, zip, assets) {
listId,
list[0],
Variable.LIST_TYPE,
- false
+ false,
+ list[3],
);
newList.value = list[1];
target.variables[newList.id] = newList;
@@ -1438,6 +1445,7 @@ const deserializeMonitor = function (monitorData, runtime, targets, extensions)
} else if (monitorData.opcode === 'data_listcontents') {
const field = monitorBlock.fields.LIST;
field.id = monitorData.id;
+ field.locked = monitorData = monitorData.locked;
field.variableType = Variable.LIST_TYPE;
}
From 57c70b40eb40da2db57896fa92fe9388f2d8543a Mon Sep 17 00:00:00 2001
From: LilyMakesThings <127533508+LilyMakesThings@users.noreply.github.com>
Date: Mon, 27 May 2024 08:05:01 +0100
Subject: [PATCH 112/202] Update package.json
---
package.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/package.json b/package.json
index b5286ec8a74..6421a3dd4cf 100644
--- a/package.json
+++ b/package.json
@@ -47,7 +47,7 @@
"format-message": "6.2.1",
"htmlparser2": "3.10.0",
"immutable": "3.8.2",
- "scratch-parser": "github:TurboWarp/scratch-parser#master",
+ "scratch-parser": "github:Unsandboxed/scratch-parser#master",
"scratch-sb1-converter": "0.2.7",
"scratch-translate-extension-languages": "0.0.20191118205314",
"text-encoding": "0.7.0",
From ab72f6a67c7ef22fce82ec10805b67062d6ad0c4 Mon Sep 17 00:00:00 2001
From: LilyMakesThings <127533508+LilyMakesThings@users.noreply.github.com>
Date: Mon, 27 May 2024 17:44:13 +0100
Subject: [PATCH 113/202] remove locking from serialization
Doing this until I can actually figure out how to get it to save. Might ask cubester or garbo.
---
src/serialization/sb3.js | 4 ----
1 file changed, 4 deletions(-)
diff --git a/src/serialization/sb3.js b/src/serialization/sb3.js
index 0dcdec568af..821a0f6095b 100644
--- a/src/serialization/sb3.js
+++ b/src/serialization/sb3.js
@@ -539,8 +539,6 @@ const serializeVariables = function (variables) {
obj.lists[varId] = [
v.name,
makeSafeForJSON(v.value),
- false,
- v.locked
];
continue;
}
@@ -696,7 +694,6 @@ const serializeMonitors = function (monitors, runtime, extensions) {
value: Array.isArray(monitorData.value) ? [] : 0,
width: monitorData.width,
height: monitorData.height,
- locked: monitorData.locked,
x: monitorData.x - xOffset,
y: monitorData.y - yOffset,
visible: monitorData.visible
@@ -1445,7 +1442,6 @@ const deserializeMonitor = function (monitorData, runtime, targets, extensions)
} else if (monitorData.opcode === 'data_listcontents') {
const field = monitorBlock.fields.LIST;
field.id = monitorData.id;
- field.locked = monitorData = monitorData.locked;
field.variableType = Variable.LIST_TYPE;
}
From f947c9fbaeebadd6e9837037b04ca52f1c71bd12 Mon Sep 17 00:00:00 2001
From: LilyMakesThings <127533508+LilyMakesThings@users.noreply.github.com>
Date: Mon, 27 May 2024 18:00:20 +0100
Subject: [PATCH 114/202] Update sb3.js
---
src/serialization/sb3.js | 9 ++-------
1 file changed, 2 insertions(+), 7 deletions(-)
diff --git a/src/serialization/sb3.js b/src/serialization/sb3.js
index 821a0f6095b..8f2d37541a8 100644
--- a/src/serialization/sb3.js
+++ b/src/serialization/sb3.js
@@ -536,10 +536,7 @@ const serializeVariables = function (variables) {
continue;
}
if (v.type === Variable.LIST_TYPE) {
- obj.lists[varId] = [
- v.name,
- makeSafeForJSON(v.value),
- ];
+ obj.lists[varId] = [v.name, makeSafeForJSON(v.value)];
continue;
}
@@ -1251,9 +1248,7 @@ const parseScratchObject = function (object, runtime, extensions, zip, assets) {
const newList = new Variable(
listId,
list[0],
- Variable.LIST_TYPE,
- false,
- list[3],
+ Variable.LIST_TYPE
);
newList.value = list[1];
target.variables[newList.id] = newList;
From f5b66e5b76ad02ad60ff9b06bd89e6476599e2a0 Mon Sep 17 00:00:00 2001
From: LilyMakesThings <127533508+LilyMakesThings@users.noreply.github.com>
Date: Mon, 27 May 2024 18:36:52 +0100
Subject: [PATCH 115/202] Fix saving (#48)
* Update sb3.js
* Update monitor-record.js
---
src/engine/monitor-record.js | 1 +
src/serialization/sb3.js | 1 +
2 files changed, 2 insertions(+)
diff --git a/src/engine/monitor-record.js b/src/engine/monitor-record.js
index 1259d525290..a117f32bb1d 100644
--- a/src/engine/monitor-record.js
+++ b/src/engine/monitor-record.js
@@ -17,6 +17,7 @@ const MonitorRecord = Record({
y: null,
width: 0,
height: 0,
+ locked: false,
visible: true
});
diff --git a/src/serialization/sb3.js b/src/serialization/sb3.js
index 8f2d37541a8..c3e7e2e3d3d 100644
--- a/src/serialization/sb3.js
+++ b/src/serialization/sb3.js
@@ -699,6 +699,7 @@ const serializeMonitors = function (monitors, runtime, extensions) {
serializedMonitor.sliderMin = monitorData.sliderMin;
serializedMonitor.sliderMax = monitorData.sliderMax;
serializedMonitor.isDiscrete = monitorData.isDiscrete;
+ serializedMonitor.locked = monitorData.locked;
}
return serializedMonitor;
})
From 2c9059f85661eb531c8fd0b24935160011315f4a Mon Sep 17 00:00:00 2001
From: LilyMakesThings
Date: Wed, 29 May 2024 04:02:15 +0100
Subject: [PATCH 116/202] rebuild
---
scratch-parser | 1 +
1 file changed, 1 insertion(+)
create mode 160000 scratch-parser
diff --git a/scratch-parser b/scratch-parser
new file mode 160000
index 00000000000..a6ba63bcc1f
--- /dev/null
+++ b/scratch-parser
@@ -0,0 +1 @@
+Subproject commit a6ba63bcc1f4194a48d15b430c1e27687cfee456
From 30f937920cc91475e803276636e97ab3f81ead95 Mon Sep 17 00:00:00 2001
From: LilyMakesThings
Date: Wed, 29 May 2024 04:03:25 +0100
Subject: [PATCH 117/202] remove submodule
---
scratch-parser | 1 -
1 file changed, 1 deletion(-)
delete mode 160000 scratch-parser
diff --git a/scratch-parser b/scratch-parser
deleted file mode 160000
index a6ba63bcc1f..00000000000
--- a/scratch-parser
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit a6ba63bcc1f4194a48d15b430c1e27687cfee456
From 90241aafdd4ad091e68d6790019ffd72c400baff Mon Sep 17 00:00:00 2001
From: LilyMakesThings <127533508+LilyMakesThings@users.noreply.github.com>
Date: Wed, 29 May 2024 08:21:59 +0100
Subject: [PATCH 118/202] something went horribly wrong here
---
src/serialization/sb3.js | 48 +++++-----------------------------------
1 file changed, 5 insertions(+), 43 deletions(-)
diff --git a/src/serialization/sb3.js b/src/serialization/sb3.js
index c3e7e2e3d3d..a8d3381bb9b 100644
--- a/src/serialization/sb3.js
+++ b/src/serialization/sb3.js
@@ -4,7 +4,6 @@
* JSON and then generates all needed scratch-vm runtime structures.
*/
-const Runtime = require('../engine/runtime');
const Blocks = require('../engine/blocks');
const Sprite = require('../sprites/sprite');
const Variable = require('../engine/variable');
@@ -699,7 +698,6 @@ const serializeMonitors = function (monitors, runtime, extensions) {
serializedMonitor.sliderMin = monitorData.sliderMin;
serializedMonitor.sliderMax = monitorData.sliderMax;
serializedMonitor.isDiscrete = monitorData.isDiscrete;
- serializedMonitor.locked = monitorData.locked;
}
return serializedMonitor;
})
@@ -806,9 +804,6 @@ const serialize = function (runtime, targetId, {allowOptimization = true} = {})
// TW: Never include full user agent to slightly improve user privacy
// if (typeof navigator !== 'undefined') meta.agent = navigator.userAgent;
- // TW: Attach copy of platform information
- meta.platform = Object.assign({}, runtime.platform);
-
// Assemble payload and return
obj.meta = meta;
@@ -1126,7 +1121,7 @@ const parseScratchAssets = function (object, runtime, zip) {
// we're always loading the 'sb3' representation of the costume
// any translation that needs to happen will happen in the process
// of building up the costume object into an sb3 format
- return runtime.wrapAssetRequest(() => deserializeCostume(costume, runtime, zip)
+ return runtime.wrapAssetRequest(deserializeCostume(costume, runtime, zip)
.then(() => loadCostume(costumeMd5Ext, costume, runtime)));
// Only attempt to load the costume after the deserialization
// process has been completed
@@ -1151,7 +1146,7 @@ const parseScratchAssets = function (object, runtime, zip) {
// we're always loading the 'sb3' representation of the costume
// any translation that needs to happen will happen in the process
// of building up the costume object into an sb3 format
- return runtime.wrapAssetRequest(() => deserializeSound(sound, runtime, zip)
+ return runtime.wrapAssetRequest(deserializeSound(sound, runtime, zip)
.then(() => loadSound(sound, runtime, assets.soundBank)));
// Only attempt to load the sound after the deserialization
// process has been completed.
@@ -1249,7 +1244,8 @@ const parseScratchObject = function (object, runtime, extensions, zip, assets) {
const newList = new Variable(
listId,
list[0],
- Variable.LIST_TYPE
+ Variable.LIST_TYPE,
+ false
);
newList.value = list[1];
target.variables[newList.id] = newList;
@@ -1482,36 +1478,6 @@ const replaceUnsafeCharsInVariableIds = function (targets) {
return targets;
};
-/**
- * @param {object} json
- * @param {Runtime} runtime
- * @returns {void|Promise} Resolves when the user has acknowledged any compatibilities, if any exist.
- */
-const checkPlatformCompatibility = (json, runtime) => {
- if (!json.meta || !json.meta.platform) {
- return;
- }
-
- const projectPlatform = json.meta.platform.name;
- if (projectPlatform === runtime.platform.name) {
- return;
- }
-
- let pending = runtime.listenerCount(Runtime.PLATFORM_MISMATCH);
- if (pending === 0) {
- return;
- }
-
- return new Promise(resolve => {
- runtime.emit(Runtime.PLATFORM_MISMATCH, json.meta.platform, () => {
- pending--;
- if (pending === 0) {
- resolve();
- }
- });
- });
-};
-
/**
* Deserialize the specified representation of a VM runtime and loads it into the provided runtime instance.
* @param {object} json - JSON representation of a VM runtime.
@@ -1520,9 +1486,7 @@ const checkPlatformCompatibility = (json, runtime) => {
* @param {boolean} isSingleSprite - If true treat as single sprite, else treat as whole project
* @returns {Promise.} Promise that resolves to the list of targets after the project is deserialized
*/
-const deserialize = async function (json, runtime, zip, isSingleSprite) {
- await checkPlatformCompatibility(json, runtime);
-
+const deserialize = function (json, runtime, zip, isSingleSprite) {
const extensions = {
extensionIDs: new Set(),
extensionURLs: new Map()
@@ -1530,10 +1494,8 @@ const deserialize = async function (json, runtime, zip, isSingleSprite) {
// Store the origin field (e.g. project originated at CSFirst) so that we can save it again.
if (json.meta && json.meta.origin) {
- // eslint-disable-next-line require-atomic-updates
runtime.origin = json.meta.origin;
} else {
- // eslint-disable-next-line require-atomic-updates
runtime.origin = null;
}
From b04cdf958c858c17526207034827cdc1d168e3b8 Mon Sep 17 00:00:00 2001
From: LilyMakesThings <127533508+LilyMakesThings@users.noreply.github.com>
Date: Wed, 29 May 2024 08:28:39 +0100
Subject: [PATCH 119/202] Update sb3.js
---
src/serialization/sb3.js | 47 +++++++++++++++++++++++++++++++++++-----
1 file changed, 42 insertions(+), 5 deletions(-)
diff --git a/src/serialization/sb3.js b/src/serialization/sb3.js
index a8d3381bb9b..8f2d37541a8 100644
--- a/src/serialization/sb3.js
+++ b/src/serialization/sb3.js
@@ -4,6 +4,7 @@
* JSON and then generates all needed scratch-vm runtime structures.
*/
+const Runtime = require('../engine/runtime');
const Blocks = require('../engine/blocks');
const Sprite = require('../sprites/sprite');
const Variable = require('../engine/variable');
@@ -804,6 +805,9 @@ const serialize = function (runtime, targetId, {allowOptimization = true} = {})
// TW: Never include full user agent to slightly improve user privacy
// if (typeof navigator !== 'undefined') meta.agent = navigator.userAgent;
+ // TW: Attach copy of platform information
+ meta.platform = Object.assign({}, runtime.platform);
+
// Assemble payload and return
obj.meta = meta;
@@ -1121,7 +1125,7 @@ const parseScratchAssets = function (object, runtime, zip) {
// we're always loading the 'sb3' representation of the costume
// any translation that needs to happen will happen in the process
// of building up the costume object into an sb3 format
- return runtime.wrapAssetRequest(deserializeCostume(costume, runtime, zip)
+ return runtime.wrapAssetRequest(() => deserializeCostume(costume, runtime, zip)
.then(() => loadCostume(costumeMd5Ext, costume, runtime)));
// Only attempt to load the costume after the deserialization
// process has been completed
@@ -1146,7 +1150,7 @@ const parseScratchAssets = function (object, runtime, zip) {
// we're always loading the 'sb3' representation of the costume
// any translation that needs to happen will happen in the process
// of building up the costume object into an sb3 format
- return runtime.wrapAssetRequest(deserializeSound(sound, runtime, zip)
+ return runtime.wrapAssetRequest(() => deserializeSound(sound, runtime, zip)
.then(() => loadSound(sound, runtime, assets.soundBank)));
// Only attempt to load the sound after the deserialization
// process has been completed.
@@ -1244,8 +1248,7 @@ const parseScratchObject = function (object, runtime, extensions, zip, assets) {
const newList = new Variable(
listId,
list[0],
- Variable.LIST_TYPE,
- false
+ Variable.LIST_TYPE
);
newList.value = list[1];
target.variables[newList.id] = newList;
@@ -1478,6 +1481,36 @@ const replaceUnsafeCharsInVariableIds = function (targets) {
return targets;
};
+/**
+ * @param {object} json
+ * @param {Runtime} runtime
+ * @returns {void|Promise} Resolves when the user has acknowledged any compatibilities, if any exist.
+ */
+const checkPlatformCompatibility = (json, runtime) => {
+ if (!json.meta || !json.meta.platform) {
+ return;
+ }
+
+ const projectPlatform = json.meta.platform.name;
+ if (projectPlatform === runtime.platform.name) {
+ return;
+ }
+
+ let pending = runtime.listenerCount(Runtime.PLATFORM_MISMATCH);
+ if (pending === 0) {
+ return;
+ }
+
+ return new Promise(resolve => {
+ runtime.emit(Runtime.PLATFORM_MISMATCH, json.meta.platform, () => {
+ pending--;
+ if (pending === 0) {
+ resolve();
+ }
+ });
+ });
+};
+
/**
* Deserialize the specified representation of a VM runtime and loads it into the provided runtime instance.
* @param {object} json - JSON representation of a VM runtime.
@@ -1486,7 +1519,9 @@ const replaceUnsafeCharsInVariableIds = function (targets) {
* @param {boolean} isSingleSprite - If true treat as single sprite, else treat as whole project
* @returns {Promise.} Promise that resolves to the list of targets after the project is deserialized
*/
-const deserialize = function (json, runtime, zip, isSingleSprite) {
+const deserialize = async function (json, runtime, zip, isSingleSprite) {
+ await checkPlatformCompatibility(json, runtime);
+
const extensions = {
extensionIDs: new Set(),
extensionURLs: new Map()
@@ -1494,8 +1529,10 @@ const deserialize = function (json, runtime, zip, isSingleSprite) {
// Store the origin field (e.g. project originated at CSFirst) so that we can save it again.
if (json.meta && json.meta.origin) {
+ // eslint-disable-next-line require-atomic-updates
runtime.origin = json.meta.origin;
} else {
+ // eslint-disable-next-line require-atomic-updates
runtime.origin = null;
}
From 784c08b1e4033eda89b3795752bfb6ab2f735693 Mon Sep 17 00:00:00 2001
From: LilyMakesThings <127533508+LilyMakesThings@users.noreply.github.com>
Date: Wed, 29 May 2024 08:29:30 +0100
Subject: [PATCH 120/202] Update tw-platform.js
---
src/engine/tw-platform.js | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/engine/tw-platform.js b/src/engine/tw-platform.js
index ee6a2e2d627..3ebad74837f 100644
--- a/src/engine/tw-platform.js
+++ b/src/engine/tw-platform.js
@@ -2,6 +2,6 @@
// This can be accessed externally on `vm.runtime.platform`
module.exports = {
- name: 'TurboWarp',
- url: 'https://turbowarp.org/'
+ name: 'Unsandboxed',
+ url: 'https://unsandboxed.org/'
};
From da14a00452082e1d433fd01c229423830910912f Mon Sep 17 00:00:00 2001
From: LilyMakesThings <127533508+LilyMakesThings@users.noreply.github.com>
Date: Wed, 29 May 2024 08:51:12 +0100
Subject: [PATCH 121/202] Update sb3.js
---
src/serialization/sb3.js | 8 --------
1 file changed, 8 deletions(-)
diff --git a/src/serialization/sb3.js b/src/serialization/sb3.js
index 8f2d37541a8..1b1309562ff 100644
--- a/src/serialization/sb3.js
+++ b/src/serialization/sb3.js
@@ -792,14 +792,6 @@ const serialize = function (runtime, targetId, {allowOptimization = true} = {})
meta.origin = runtime.origin;
}
- // USB: A few mods have agreed to list our platform's name inside of the project json.
- // We also add a couple more bits specific to Unsandboxed.
- const platformMeta = Object.create(null);
- platformMeta.name = 'Unsandboxed';
- platformMeta.url = 'https://unsandboxed.org/';
- platformMeta.version = 'alpha';
- meta.platform = platformMeta;
-
// Attach full user agent string to metadata if available
meta.agent = '';
// TW: Never include full user agent to slightly improve user privacy
From 17c7a9cd3cef0aef172532e73aff155a7fcbc0b3 Mon Sep 17 00:00:00 2001
From: LilyMakesThings <127533508+LilyMakesThings@users.noreply.github.com>
Date: Wed, 29 May 2024 09:26:29 +0100
Subject: [PATCH 122/202] random change to start workflow i know this isnt
smart
---
src/serialization/sb3.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/serialization/sb3.js b/src/serialization/sb3.js
index 1b1309562ff..9437b7cd756 100644
--- a/src/serialization/sb3.js
+++ b/src/serialization/sb3.js
@@ -1351,7 +1351,7 @@ const deserializeMonitor = function (monitorData, runtime, targets, extensions)
}
// Get information about this monitor, if it exists, given the monitor's opcode.
- // This will be undefined for extension blocks
+ // This will be undefined for extension blocks.
const monitorBlockInfo = runtime.monitorBlockInfo[monitorData.opcode];
// Due to a bug (see https://github.com/scratchfoundation/scratch-vm/pull/2322), renamed list monitors may have been serialized
From 0ea11652ff4edafed49852ea96e4db2f3dac546e Mon Sep 17 00:00:00 2001
From: LilyMakesThings <127533508+LilyMakesThings@users.noreply.github.com>
Date: Wed, 29 May 2024 17:15:08 +0100
Subject: [PATCH 123/202] Update package.json
---
package.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/package.json b/package.json
index 6421a3dd4cf..b5286ec8a74 100644
--- a/package.json
+++ b/package.json
@@ -47,7 +47,7 @@
"format-message": "6.2.1",
"htmlparser2": "3.10.0",
"immutable": "3.8.2",
- "scratch-parser": "github:Unsandboxed/scratch-parser#master",
+ "scratch-parser": "github:TurboWarp/scratch-parser#master",
"scratch-sb1-converter": "0.2.7",
"scratch-translate-extension-languages": "0.0.20191118205314",
"text-encoding": "0.7.0",
From 2a255558f59297ccbce4cb41524681a403184d0d Mon Sep 17 00:00:00 2001
From: LilyMakesThings <127533508+LilyMakesThings@users.noreply.github.com>
Date: Wed, 29 May 2024 17:25:21 +0100
Subject: [PATCH 124/202] another ditch attempt
---
src/serialization/sb3.js | 7 +++----
1 file changed, 3 insertions(+), 4 deletions(-)
diff --git a/src/serialization/sb3.js b/src/serialization/sb3.js
index 9437b7cd756..815c2837a4d 100644
--- a/src/serialization/sb3.js
+++ b/src/serialization/sb3.js
@@ -45,7 +45,6 @@ const INPUT_DIFF_BLOCK_SHADOW = 3; // obscured shadow
// Constants used during deserialization of an SB3 file
const CORE_EXTENSIONS = [
'argument',
- 'camera',
'colour',
'control',
'data',
@@ -56,7 +55,6 @@ const CORE_EXTENSIONS = [
'operator',
'procedures',
'sensing',
- 'string',
'sound'
];
@@ -1240,7 +1238,8 @@ const parseScratchObject = function (object, runtime, extensions, zip, assets) {
const newList = new Variable(
listId,
list[0],
- Variable.LIST_TYPE
+ Variable.LIST_TYPE,
+ false
);
newList.value = list[1];
target.variables[newList.id] = newList;
@@ -1351,7 +1350,7 @@ const deserializeMonitor = function (monitorData, runtime, targets, extensions)
}
// Get information about this monitor, if it exists, given the monitor's opcode.
- // This will be undefined for extension blocks.
+ // This will be undefined for extension blocks
const monitorBlockInfo = runtime.monitorBlockInfo[monitorData.opcode];
// Due to a bug (see https://github.com/scratchfoundation/scratch-vm/pull/2322), renamed list monitors may have been serialized
From 2d88620192ec5763cf43b18534fa1e25d5ca9440 Mon Sep 17 00:00:00 2001
From: LilyMakesThings <127533508+LilyMakesThings@users.noreply.github.com>
Date: Wed, 29 May 2024 17:26:27 +0100
Subject: [PATCH 125/202] add back camera and string to core exts
---
src/serialization/sb3.js | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/src/serialization/sb3.js b/src/serialization/sb3.js
index 815c2837a4d..5b334ddee11 100644
--- a/src/serialization/sb3.js
+++ b/src/serialization/sb3.js
@@ -45,6 +45,7 @@ const INPUT_DIFF_BLOCK_SHADOW = 3; // obscured shadow
// Constants used during deserialization of an SB3 file
const CORE_EXTENSIONS = [
'argument',
+ 'camera',
'colour',
'control',
'data',
@@ -55,7 +56,8 @@ const CORE_EXTENSIONS = [
'operator',
'procedures',
'sensing',
- 'sound'
+ 'sound',
+ 'string'
];
// Constants referring to 'primitive' blocks that are usually shadows,
From 72fe25ffd1556851af6cde542c4e13794d3f2e83 Mon Sep 17 00:00:00 2001
From: LilyMakesThings <127533508+LilyMakesThings@users.noreply.github.com>
Date: Wed, 29 May 2024 17:28:52 +0100
Subject: [PATCH 126/202] remove locked state from xml
---
src/engine/variable.js | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/src/engine/variable.js b/src/engine/variable.js
index 50b2e347c28..a3d6478ddca 100644
--- a/src/engine/variable.js
+++ b/src/engine/variable.js
@@ -38,8 +38,7 @@ class Variable {
toXML (isLocal) {
isLocal = (isLocal === true);
return `${xmlEscape(this.name)}`;
+ }" iscloud="${this.isCloud}">${xmlEscape(this.name)}`;
}
/**
From 039c4e05f28bee4cbb155bc18a5aacb73b0275c2 Mon Sep 17 00:00:00 2001
From: LilyMakesThings <127533508+LilyMakesThings@users.noreply.github.com>
Date: Wed, 5 Jun 2024 07:34:41 +0100
Subject: [PATCH 127/202] export value type in visual report (#49)
---
src/engine/runtime.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/engine/runtime.js b/src/engine/runtime.js
index 709b5e722c3..4cbc6c5a4c4 100644
--- a/src/engine/runtime.js
+++ b/src/engine/runtime.js
@@ -3237,7 +3237,7 @@ class Runtime extends EventEmitter {
* @param {string} value Value to show associated with the block.
*/
visualReport (blockId, value) {
- this.emit(Runtime.VISUAL_REPORT, {id: blockId, value: String(value)});
+ this.emit(Runtime.VISUAL_REPORT, {id: blockId, value: String(value), type: typeof value});
}
/**
From 73bef55e0142a02d885e6ad4144c9718de266572 Mon Sep 17 00:00:00 2001
From: Ashime Sho <135030944+Ashimee@users.noreply.github.com>
Date: Mon, 17 Jun 2024 09:43:06 -0400
Subject: [PATCH 128/202] Shut up eslint (#50)
* Fix ESLINT +1
* Fix ESLINT +2
---------
Co-authored-by: Ashime
---
src/blocks/scratch3_camera.js | 18 ++++++++----------
src/blocks/scratch3_event.js | 2 +-
src/blocks/scratch3_string.js | 1 -
src/compiler/irgen.js | 6 +++++-
src/compiler/jsgen.js | 1 +
src/engine/blocks.js | 18 +++++++++---------
src/engine/execute.js | 6 +++---
src/engine/runtime.js | 2 +-
src/extension-support/extension-manager.js | 2 +-
src/extensions/tw/index.js | 2 +-
10 files changed, 30 insertions(+), 28 deletions(-)
diff --git a/src/blocks/scratch3_camera.js b/src/blocks/scratch3_camera.js
index 29fe16def24..399ff987528 100644
--- a/src/blocks/scratch3_camera.js
+++ b/src/blocks/scratch3_camera.js
@@ -1,6 +1,4 @@
const Cast = require('../util/cast');
-const MathUtil = require('../util/math-util');
-const Timer = require('../util/timer');
class Scratch3CameraBlocks {
constructor (runtime) {
@@ -39,13 +37,13 @@ class Scratch3CameraBlocks {
};
}
- moveToXY (args, util) {
+ moveToXY (args) {
const x = Cast.toNumber(args.X);
const y = Cast.toNumber(args.Y);
this.runtime.camera.setXY(x, y);
}
- changeByXY (args, util) {
+ changeByXY (args) {
const x = Cast.toNumber(args.X);
const y = Cast.toNumber(args.Y);
const newX = x + this.runtime.camera.x;
@@ -53,33 +51,33 @@ class Scratch3CameraBlocks {
this.runtime.camera.setXY(newX, newY);
}
- setX (args, util) {
+ setX (args) {
const x = Cast.toNumber(args.X);
this.runtime.camera.setXY(x, this.runtime.camera.y);
}
- changeX (args, util) {
+ changeX (args) {
const x = Cast.toNumber(args.X);
const newX = x + this.runtime.camera.x;
this.runtime.camera.setXY(newX, this.runtime.camera.y);
}
- setY (args, util) {
+ setY (args) {
const y = Cast.toNumber(args.Y);
this.runtime.camera.setXY(this.runtime.camera.x, y);
}
- changeY (args, util) {
+ changeY (args) {
const y = Cast.toNumber(args.Y);
const newY = y + this.runtime.camera.y;
this.runtime.camera.setXY(this.runtime.camera.x, newY);
}
- getCameraX (args, util) {
+ getCameraX () {
return this.runtime.camera.x;
}
- getCameraY (args, util) {
+ getCameraY () {
return this.runtime.camera.y;
}
}
diff --git a/src/blocks/scratch3_event.js b/src/blocks/scratch3_event.js
index cf598829f43..5bfad8217f7 100644
--- a/src/blocks/scratch3_event.js
+++ b/src/blocks/scratch3_event.js
@@ -67,7 +67,7 @@ class Scratch3EventBlocks {
};
}
- when (args, util) {
+ when (args) {
const condition = Cast.toBoolean(args.CONDITION);
return condition;
}
diff --git a/src/blocks/scratch3_string.js b/src/blocks/scratch3_string.js
index 783da2218f7..ed8a32ac67a 100644
--- a/src/blocks/scratch3_string.js
+++ b/src/blocks/scratch3_string.js
@@ -1,5 +1,4 @@
const Cast = require('../util/cast.js');
-const MathUtil = require('../util/math-util.js');
class Scratch3StringBlocks {
constructor (runtime) {
diff --git a/src/compiler/irgen.js b/src/compiler/irgen.js
index bfe9bb0b006..2ea67ff216f 100644
--- a/src/compiler/irgen.js
+++ b/src/compiler/irgen.js
@@ -757,7 +757,11 @@ class ScriptTreeGenerator {
const blockInfo = this.getBlockInfo(block.opcode);
if (blockInfo) {
const type = blockInfo.info.blockType;
- if (type === BlockType.ARRAY || type === BlockType.OBJECT || type === BlockType.REPORTER || type === BlockType.BOOLEAN || type === BlockType.INLINE) {
+ if (
+ type === BlockType.ARRAY || type === BlockType.OBJECT ||
+ type === BlockType.REPORTER || type === BlockType.BOOLEAN ||
+ type === BlockType.INLINE
+ ) {
return this.descendCompatLayer(block);
}
}
diff --git a/src/compiler/jsgen.js b/src/compiler/jsgen.js
index 9faaae31d48..5460a7f60eb 100644
--- a/src/compiler/jsgen.js
+++ b/src/compiler/jsgen.js
@@ -925,6 +925,7 @@ class JSGenerator {
break;
case 'control.allAtOnce':
+ // eslint-disable-next-line no-case-declarations
const previousWarp = this.isWarp;
this.isWarp = true;
this.descendStack(node.code, new Frame(false, 'control.allAtOnce'));
diff --git a/src/engine/blocks.js b/src/engine/blocks.js
index e5b9410a113..bc58e876b8e 100644
--- a/src/engine/blocks.js
+++ b/src/engine/blocks.js
@@ -711,14 +711,7 @@ class Blocks {
// Update block value
if (!block.fields[args.name]) return;
- if (typeof block.fields[args.name].variableType !== 'undefined') {
- // Get variable name using the id in args.value.
- const variable = this.runtime.getEditingTarget().lookupVariableById(args.value);
- if (variable) {
- block.fields[args.name].value = variable.name;
- block.fields[args.name].id = args.value;
- }
- } else {
+ if (typeof block.fields[args.name].variableType === 'undefined') {
// Changing the value in a dropdown
block.fields[args.name].value = args.value;
@@ -741,6 +734,13 @@ class Blocks {
params: this._getBlockParams(flyoutBlock)
}));
}
+ } else {
+ // Get variable name using the id in args.value.
+ const variable = this.runtime.getEditingTarget().lookupVariableById(args.value);
+ if (variable) {
+ block.fields[args.name].value = variable.name;
+ block.fields[args.name].id = args.value;
+ }
}
break;
case 'mutation':
@@ -979,7 +979,7 @@ class Blocks {
const variableBlocks = [];
for (const blockId in this._blocks) {
- if (!this._blocks.hasOwnProperty(blockId)) continue;
+ if (!Object.prototype.hasOwnProperty.call(this._blocks, blockId)) continue;
const block = this._blocks[blockId];
// Check for blocks with fields referencing variable/list, otherwise variable/list reporters
if (block.fields[fieldName] &&
diff --git a/src/engine/execute.js b/src/engine/execute.js
index fc22789ae4e..1c9e7e72a00 100644
--- a/src/engine/execute.js
+++ b/src/engine/execute.js
@@ -313,13 +313,13 @@ class BlockCached {
// Store the static fields onto _argValues.
for (const fieldName in fields) {
- if (typeof fields[fieldName].variableType !== 'undefined') {
+ if (typeof fields[fieldName].variableType === 'undefined') {
+ this._argValues[fieldName] = fields[fieldName].value;
+ } else {
this._argValues[fieldName] = {
id: fields[fieldName].id,
name: fields[fieldName].value
};
- } else {
- this._argValues[fieldName] = fields[fieldName].value;
}
}
diff --git a/src/engine/runtime.js b/src/engine/runtime.js
index 4cbc6c5a4c4..bdd21174011 100644
--- a/src/engine/runtime.js
+++ b/src/engine/runtime.js
@@ -1566,7 +1566,7 @@ class Runtime extends EventEmitter {
const mutation = blockInfo.isDynamic ? `` : '';
const inputs = context.inputList.join('');
const toolboxIdXml = blockInfo.isDynamic && blockInfo.paletteKey ?
- ` id="${xmlEscape(blockInfo.paletteKey)}"` : '';
+ ` id="${xmlEscape(blockInfo.paletteKey)}"` : '';
const blockXML = `${mutation}${inputs}`;
if (blockInfo.extensions) {
diff --git a/src/extension-support/extension-manager.js b/src/extension-support/extension-manager.js
index cb1b9200f8b..236c41a3b32 100644
--- a/src/extension-support/extension-manager.js
+++ b/src/extension-support/extension-manager.js
@@ -494,7 +494,7 @@ class ExtensionManager {
const menuState = {
selectedValue: scratchBlocksMenuObject.getValue(),
sourceBlock: scratchBlocksMenuObject.sourceBlock_,
- state: scratchBlocksMenuObject,
+ state: scratchBlocksMenuObject
};
// TODO: Fix this to use dispatch.call when extensions are running in workers.
diff --git a/src/extensions/tw/index.js b/src/extensions/tw/index.js
index 465c1b60347..97e3e11c8db 100644
--- a/src/extensions/tw/index.js
+++ b/src/extensions/tw/index.js
@@ -3,7 +3,7 @@ const BlockType = require('../../extension-support/block-type');
const ArgumentType = require('../../extension-support/argument-type');
const Cast = require('../../util/cast');
-// eslint-disable-next-line max-len
+// eslint-disable-next-line
const iconURI = `data:image/svg+xml;base64,${btoa('')}`;
/**
From d73611ce395b9e4e7efaed2cde81ded417e3a648 Mon Sep 17 00:00:00 2001
From: Ashime Sho
Date: Thu, 19 Sep 2024 19:35:26 -0400
Subject: [PATCH 129/202] Actually delete args.stringNumber
---
src/compiler/jsgen.js | 2 --
1 file changed, 2 deletions(-)
diff --git a/src/compiler/jsgen.js b/src/compiler/jsgen.js
index 1ea900f81b5..40ab5631788 100644
--- a/src/compiler/jsgen.js
+++ b/src/compiler/jsgen.js
@@ -433,8 +433,6 @@ class JSGenerator {
case 'addons.call':
return new TypedInput(`(${this.descendAddonCall(node)})`, TYPE_UNKNOWN);
- case 'args.stringNumber':
- return new TypedInput(`p${node.index}`, TYPE_UNKNOWN);
case 'args.parameter':
return new TypedInput(`(thread.getParam("${node.name}") ?? 0)`, TYPE_UNKNOWN);
From 75ced47ff982b201201c25509964d7498adf0d90 Mon Sep 17 00:00:00 2001
From: Ashime Sho
Date: Thu, 19 Sep 2024 20:10:42 -0400
Subject: [PATCH 130/202] iwnafhwtb for runtime
---
src/engine/runtime.js | 13 +++++++++++++
1 file changed, 13 insertions(+)
diff --git a/src/engine/runtime.js b/src/engine/runtime.js
index ef8e490e8f2..55b6b50a4ac 100644
--- a/src/engine/runtime.js
+++ b/src/engine/runtime.js
@@ -584,6 +584,19 @@ class Runtime extends EventEmitter {
* Total number of finished or errored scratch-storage load() requests since the runtime was created or cleared.
*/
this.finishedAssetRequests = 0;
+
+ /**
+ * Export some internal values for extensions.
+ */
+ this.exports = {
+ i_will_not_ask_for_help_when_these_break: () => {
+ console.warn('You are using unsupported APIs. WHEN your code breaks, do not expect help.');
+ return ({
+ ArgumentTypeMap,
+ FieldTypeMap
+ });
+ }
+ };
}
/**
From 97689b9e78b02bd419a24f96f66ab65c51b2d957 Mon Sep 17 00:00:00 2001
From: Ashime Sho
Date: Thu, 19 Sep 2024 20:16:53 -0400
Subject: [PATCH 131/202] Block shape support
---
src/engine/runtime.js | 4 +++
src/extension-support/block-shape.js | 25 +++++++++++++++++++
.../tw-extension-api-common.js | 2 ++
3 files changed, 31 insertions(+)
create mode 100644 src/extension-support/block-shape.js
diff --git a/src/engine/runtime.js b/src/engine/runtime.js
index 55b6b50a4ac..36da7c55145 100644
--- a/src/engine/runtime.js
+++ b/src/engine/runtime.js
@@ -1520,6 +1520,10 @@ class Runtime extends EventEmitter {
break;
}
+ if (blockInfo.blockShape ?? blockInfo.outputShape) {
+ blockJSON.outputShape = blockInfo.blockShape ?? blockInfo.outputShape;
+ }
+
const blockText = Array.isArray(blockInfo.text) ? blockInfo.text : [blockInfo.text];
let inTextNum = 0; // text for the next block "arm" is blockText[inTextNum]
let inBranchNum = 0; // how many branches have we placed into the JSON so far?
diff --git a/src/extension-support/block-shape.js b/src/extension-support/block-shape.js
new file mode 100644
index 00000000000..854975ada88
--- /dev/null
+++ b/src/extension-support/block-shape.js
@@ -0,0 +1,25 @@
+// Use the constants instead of manually redefining them again
+const ScratchBlocksConstants = require('../engine/scratch-blocks-constants');
+
+/**
+ * Types of block shapes
+ * @enum {number}
+ */
+const BlockShape = {
+ /**
+ * Output shape: hexagonal (booleans/predicates).
+ */
+ HEXAGONAL: ScratchBlocksConstants.OUTPUT_SHAPE_HEXAGONAL,
+
+ /**
+ * Output shape: rounded (numbers).
+ */
+ ROUND: ScratchBlocksConstants.OUTPUT_SHAPE_ROUND,
+
+ /**
+ * Output shape: squared (any/all values; strings).
+ */
+ SQUARE: ScratchBlocksConstants.OUTPUT_SHAPE_SQUARE
+};
+
+module.exports = BlockShape;
diff --git a/src/extension-support/tw-extension-api-common.js b/src/extension-support/tw-extension-api-common.js
index a0e49dc3f93..40d5669e99d 100644
--- a/src/extension-support/tw-extension-api-common.js
+++ b/src/extension-support/tw-extension-api-common.js
@@ -1,5 +1,6 @@
const ArgumentType = require('./argument-type');
const BlockType = require('./block-type');
+const BlockShape = require('./block-shape');
const TargetType = require('./target-type');
const ContextMenuContext = require('./context-menu-context');
const Cast = require('../util/cast');
@@ -8,6 +9,7 @@ const Util = require('../util/usb-util');
const Scratch = {
ArgumentType,
BlockType,
+ BlockShape,
TargetType,
ContextMenuContext,
Cast,
From abe5f464a37f397bc738fbcf46400262aa2172e1 Mon Sep 17 00:00:00 2001
From: Ashime Sho
Date: Thu, 19 Sep 2024 20:19:07 -0400
Subject: [PATCH 132/202] Export ScratchBlocksConstants
---
src/engine/runtime.js | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/engine/runtime.js b/src/engine/runtime.js
index 36da7c55145..6fe9986e997 100644
--- a/src/engine/runtime.js
+++ b/src/engine/runtime.js
@@ -592,6 +592,7 @@ class Runtime extends EventEmitter {
i_will_not_ask_for_help_when_these_break: () => {
console.warn('You are using unsupported APIs. WHEN your code breaks, do not expect help.');
return ({
+ ScratchBlocksConstants,
ArgumentTypeMap,
FieldTypeMap
});
From ba574fa031c48ab2bb2e10ae20b0c1907f768b91 Mon Sep 17 00:00:00 2001
From: Ashime Sho
Date: Thu, 19 Sep 2024 20:25:50 -0400
Subject: [PATCH 133/202] Mutator and output options
---
src/engine/runtime.js | 9 +++++++++
1 file changed, 9 insertions(+)
diff --git a/src/engine/runtime.js b/src/engine/runtime.js
index 6fe9986e997..1ba86028510 100644
--- a/src/engine/runtime.js
+++ b/src/engine/runtime.js
@@ -1525,6 +1525,15 @@ class Runtime extends EventEmitter {
blockJSON.outputShape = blockInfo.blockShape ?? blockInfo.outputShape;
}
+ if (Object.prototype.hasOwnProperty.call(blockInfo, 'output')) {
+ blockJSON.output = blockInfo.output;
+ }
+
+ if (blockInfo.mutator) {
+ blockJSON.mutator = blockInfo.mutator;
+ }
+
+
const blockText = Array.isArray(blockInfo.text) ? blockInfo.text : [blockInfo.text];
let inTextNum = 0; // text for the next block "arm" is blockText[inTextNum]
let inBranchNum = 0; // how many branches have we placed into the JSON so far?
From 5a9b186858727869e10bc8f09949227523a6cb0b Mon Sep 17 00:00:00 2001
From: Ashime Sho
Date: Thu, 19 Sep 2024 20:28:13 -0400
Subject: [PATCH 134/202] Inline support in the compiler
---
src/compiler/jsgen.js | 30 ++++++++++++++++++++++++++++++
1 file changed, 30 insertions(+)
diff --git a/src/compiler/jsgen.js b/src/compiler/jsgen.js
index 40ab5631788..f13be803c1b 100644
--- a/src/compiler/jsgen.js
+++ b/src/compiler/jsgen.js
@@ -437,6 +437,26 @@ class JSGenerator {
return new TypedInput(`(thread.getParam("${node.name}") ?? 0)`, TYPE_UNKNOWN);
case 'compat':
+ if (node.blockType === BlockType.INLINE) {
+ const branchVariable = this.localVariables.next();
+ const returnVariable = this.localVariables.next();
+ let source = '(yield* (function*() {\n';
+ source += `let ${returnVariable} = undefined;\n`;
+ source += `const ${branchVariable} = createBranchInfo(false);\n`;
+ source += `${returnVariable} = (${this.generateCompatibilityLayerCall(node, false, branchVariable)});\n`;
+ source += `${branchVariable}.branch = globalState.blockUtility._startedBranch[0];\n`;
+ source += `switch (${branchVariable}.branch) {\n`;
+ for (const index in node.substacks) {
+ source += `case ${+index}: {\n`;
+ source += this.descendStackForSource(node.substacks[index], new Frame(false));
+ source += `break;\n`;
+ source += `}\n`; // close case
+ }
+ source += '}\n'; // close switch
+ source += `return ${returnVariable};\n`;
+ source += '})())'; // close function and yield
+ return new TypedInput(source, TYPE_UNKNOWN);
+ }
// Compatibility layer inputs never use flags.
return new TypedInput(`(${this.generateCompatibilityLayerCall(node, false)})`, TYPE_UNKNOWN);
@@ -1270,6 +1290,16 @@ class JSGenerator {
this.popFrame();
}
+ descendStackForSource (nodes, frame) {
+ // Wrapper for descendStack to get the source
+ const oldSource = this.source;
+ this.source = '';
+ this.descendStack(nodes, frame);
+ const stackSource = this.source;
+ this.source = oldSource;
+ return stackSource;
+ }
+
descendVariable (variable) {
if (Object.prototype.hasOwnProperty.call(this.variableInputs, variable.id)) {
return this.variableInputs[variable.id];
From a0d43b054f047e5d8a5a9e91c08ddd45c7f1272e Mon Sep 17 00:00:00 2001
From: Ashime Sho
Date: Thu, 19 Sep 2024 20:35:18 -0400
Subject: [PATCH 135/202] branch on end support (compiler part)
---
src/compiler/compat-block-utility.js | 6 ++++--
src/compiler/jsexecute.js | 5 +++--
src/compiler/jsgen.js | 2 ++
3 files changed, 9 insertions(+), 4 deletions(-)
diff --git a/src/compiler/compat-block-utility.js b/src/compiler/compat-block-utility.js
index 6fedba1d9d2..6b9033a2f46 100644
--- a/src/compiler/compat-block-utility.js
+++ b/src/compiler/compat-block-utility.js
@@ -10,7 +10,8 @@ class CompatibilityLayerBlockUtility extends BlockUtility {
return this.thread.compatibilityStackFrame;
}
- startBranch (branchNumber, isLoop) {
+ startBranch (branchNumber, isLoop, onEnd) {
+ if (this._branchInfo && onEnd) this._branchInfo.onEnd.push(onEnd);
this._startedBranch = [branchNumber, isLoop];
}
@@ -29,10 +30,11 @@ class CompatibilityLayerBlockUtility extends BlockUtility {
throw new Error('getParam is not supported by this BlockUtility');
}
- init (thread, fakeBlockId, stackFrame) {
+ init (thread, fakeBlockId, stackFrame, branchInfo) {
this.thread = thread;
this.sequencer = thread.target.runtime.sequencer;
this._startedBranch = null;
+ this._branchInfo = branchInfo;
thread.stack[0] = fakeBlockId;
thread.compatibilityStackFrame = stackFrame;
}
diff --git a/src/compiler/jsexecute.js b/src/compiler/jsexecute.js
index d1122e458c2..2e38ed10c7d 100644
--- a/src/compiler/jsexecute.js
+++ b/src/compiler/jsexecute.js
@@ -152,7 +152,7 @@ const executeInCompatibilityLayer = function*(inputs, blockFunction, isWarp, use
};
const executeBlock = () => {
- blockUtility.init(thread, blockId, stackFrame);
+ blockUtility.init(thread, blockId, stackFrame, branchInfo);
return blockFunction(inputs, blockUtility);
};
@@ -207,7 +207,8 @@ runtimeFunctions.createBranchInfo = `const createBranchInfo = (isLoop) => ({
defaultIsLoop: isLoop,
isLoop: false,
branch: 0,
- stackFrame: {}
+ stackFrame: {},
+ onEnd: []
});`;
/**
diff --git a/src/compiler/jsgen.js b/src/compiler/jsgen.js
index f13be803c1b..a741a89ddf8 100644
--- a/src/compiler/jsgen.js
+++ b/src/compiler/jsgen.js
@@ -453,6 +453,7 @@ class JSGenerator {
source += `}\n`; // close case
}
source += '}\n'; // close switch
+ source += `if (${branchVariable}.onEnd[0]) yield ${branchVariable}.onEnd.shift()(${branchVariable});\n`;
source += `return ${returnVariable};\n`;
source += '})())'; // close function and yield
return new TypedInput(source, TYPE_UNKNOWN);
@@ -846,6 +847,7 @@ class JSGenerator {
this.source += `}\n`; // close case
}
this.source += '}\n'; // close switch
+ this.source += `if (${branchVariable}.onEnd[0]) yield ${branchVariable}.onEnd.shift()(${branchVariable});\n`;
this.source += `if (!${branchVariable}.isLoop) break;\n`;
this.yieldLoop();
this.source += '}\n'; // close while
From 56a43adf68241e39079ff0b7d58d09e04afc3031 Mon Sep 17 00:00:00 2001
From: Ashime Sho
Date: Thu, 19 Sep 2024 20:37:17 -0400
Subject: [PATCH 136/202] USB object and array shape support
---
src/extension-support/block-shape.js | 12 +++++++++++-
1 file changed, 11 insertions(+), 1 deletion(-)
diff --git a/src/extension-support/block-shape.js b/src/extension-support/block-shape.js
index 854975ada88..3cd0218521c 100644
--- a/src/extension-support/block-shape.js
+++ b/src/extension-support/block-shape.js
@@ -19,7 +19,17 @@ const BlockShape = {
/**
* Output shape: squared (any/all values; strings).
*/
- SQUARE: ScratchBlocksConstants.OUTPUT_SHAPE_SQUARE
+ SQUARE: ScratchBlocksConstants.OUTPUT_SHAPE_SQUARE,
+
+ /**
+ * Output shape: array (this is just squared as of now)
+ */
+ ARRAY: ScratchBlocksConstants.OUTPUT_SHAPE_ARRAY,
+
+ /**
+ * Output shape: object (objects)
+ */
+ OBJECT: ScratchBlockConstants.OUTPUT_SHAPE_OBJECT,
};
module.exports = BlockShape;
From c63ff1f9088681cd6ba455973dabaf303d9ef9b5 Mon Sep 17 00:00:00 2001
From: Ashime Sho
Date: Thu, 19 Sep 2024 20:46:45 -0400
Subject: [PATCH 137/202] ESLint
---
src/engine/runtime.js | 6 +++---
src/extension-support/block-shape.js | 2 +-
2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/src/engine/runtime.js b/src/engine/runtime.js
index 1ba86028510..10b2b5c8e9c 100644
--- a/src/engine/runtime.js
+++ b/src/engine/runtime.js
@@ -1522,15 +1522,15 @@ class Runtime extends EventEmitter {
}
if (blockInfo.blockShape ?? blockInfo.outputShape) {
- blockJSON.outputShape = blockInfo.blockShape ?? blockInfo.outputShape;
+ blockJSON.outputShape = blockInfo.blockShape ?? blockInfo.outputShape;
}
if (Object.prototype.hasOwnProperty.call(blockInfo, 'output')) {
- blockJSON.output = blockInfo.output;
+ blockJSON.output = blockInfo.output;
}
if (blockInfo.mutator) {
- blockJSON.mutator = blockInfo.mutator;
+ blockJSON.mutator = blockInfo.mutator;
}
diff --git a/src/extension-support/block-shape.js b/src/extension-support/block-shape.js
index 3cd0218521c..6a8eba3316d 100644
--- a/src/extension-support/block-shape.js
+++ b/src/extension-support/block-shape.js
@@ -29,7 +29,7 @@ const BlockShape = {
/**
* Output shape: object (objects)
*/
- OBJECT: ScratchBlockConstants.OUTPUT_SHAPE_OBJECT,
+ OBJECT: ScratchBlocksConstants.OUTPUT_SHAPE_OBJECT
};
module.exports = BlockShape;
From c05f1c79e0e8745371c658c9accd2c61fdf68941 Mon Sep 17 00:00:00 2001
From: Ashime Sho
Date: Thu, 19 Sep 2024 20:54:22 -0400
Subject: [PATCH 138/202] Fix ESLint (final?)
---
src/compiler/jsgen.js | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/src/compiler/jsgen.js b/src/compiler/jsgen.js
index 04058941787..ebe30076ff2 100644
--- a/src/compiler/jsgen.js
+++ b/src/compiler/jsgen.js
@@ -626,8 +626,8 @@ class JSGenerator {
const right = this.descendInput(node.right);
return new TypedInput(`(${left.asUnknown()} <= ${right.asUnknown()})`, TYPE_BOOLEAN);
}
- //case 'op.letterOf':
- // return new TypedInput(`((${this.descendInput(node.string).asString()})[(${this.descendInput(node.letter).asNumber()} | 0) - 1] || "")`, TYPE_STRING);
+ // case 'op.letterOf':
+ // return new TypedInput(`((${this.descendInput(node.string).asString()})[(${this.descendInput(node.letter).asNumber()} | 0) - 1] || "")`, TYPE_STRING);
case 'op.lettersOf':
return new TypedInput(`((${this.descendInput(node.string).asString()}).substring(${this.descendInput(node.left).asNumber() - 1}, ${this.descendInput(node.right).asNumber()}) || "")`, TYPE_STRING);
case 'op.ln':
From 7038579ca076640155334f196f44264d793442eb Mon Sep 17 00:00:00 2001
From: Ashime Sho
Date: Fri, 20 Sep 2024 13:22:15 -0400
Subject: [PATCH 139/202] Fix locked state not saving (2/2)
---
package-lock.json | 5 ++---
package.json | 2 +-
src/serialization/sb3.js | 7 +++++--
3 files changed, 8 insertions(+), 6 deletions(-)
diff --git a/package-lock.json b/package-lock.json
index fb08394a736..a48e799f0cd 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -22,7 +22,7 @@
"format-message": "6.2.1",
"htmlparser2": "3.10.0",
"immutable": "3.8.2",
- "scratch-parser": "github:TurboWarp/scratch-parser#master",
+ "scratch-parser": "github:Unsandboxed/scratch-parser",
"scratch-sb1-converter": "0.2.7",
"scratch-translate-extension-languages": "0.0.20191118205314",
"text-encoding": "0.7.0",
@@ -13379,8 +13379,7 @@
},
"node_modules/scratch-parser": {
"version": "0.0.0-development",
- "resolved": "git+ssh://git@github.com/TurboWarp/scratch-parser.git#9911fe8e6b5aed34ea19aff5490c3948d1523fc5",
- "license": "MPL-2.0",
+ "resolved": "git+ssh://git@github.com/Unsandboxed/scratch-parser.git#b8228911219887a25f8154081f2dec24c3664ba4",
"dependencies": {
"@turbowarp/json": "^0.1.1",
"@turbowarp/jszip": "^3.11.0",
diff --git a/package.json b/package.json
index b5286ec8a74..2db368f77f1 100644
--- a/package.json
+++ b/package.json
@@ -47,7 +47,7 @@
"format-message": "6.2.1",
"htmlparser2": "3.10.0",
"immutable": "3.8.2",
- "scratch-parser": "github:TurboWarp/scratch-parser#master",
+ "scratch-parser": "github:Unsandboxed/scratch-parser",
"scratch-sb1-converter": "0.2.7",
"scratch-translate-extension-languages": "0.0.20191118205314",
"text-encoding": "0.7.0",
diff --git a/src/serialization/sb3.js b/src/serialization/sb3.js
index 5b334ddee11..217822c6235 100644
--- a/src/serialization/sb3.js
+++ b/src/serialization/sb3.js
@@ -536,7 +536,7 @@ const serializeVariables = function (variables) {
continue;
}
if (v.type === Variable.LIST_TYPE) {
- obj.lists[varId] = [v.name, makeSafeForJSON(v.value)];
+ obj.lists[varId] = [v.name, makeSafeForJSON(v.value), v.locked];
continue;
}
@@ -695,7 +695,9 @@ const serializeMonitors = function (monitors, runtime, extensions) {
y: monitorData.y - yOffset,
visible: monitorData.visible
};
- if (monitorData.mode !== 'list') {
+ if (monitorData.mode === 'list') {
+ serializedMonitor.locked = monitorData.locked;
+ } else {
serializedMonitor.sliderMin = monitorData.sliderMin;
serializedMonitor.sliderMax = monitorData.sliderMax;
serializedMonitor.isDiscrete = monitorData.isDiscrete;
@@ -1244,6 +1246,7 @@ const parseScratchObject = function (object, runtime, extensions, zip, assets) {
false
);
newList.value = list[1];
+ newList.locked = list[2] || false;
target.variables[newList.id] = newList;
}
}
From b94433c4d3de5c49f9789a39b2aae91fe402c0ee Mon Sep 17 00:00:00 2001
From: Ashime Sho
Date: Fri, 20 Sep 2024 14:08:41 -0400
Subject: [PATCH 140/202] Tooltips and warnings (VM part)
---
src/engine/runtime.js | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/src/engine/runtime.js b/src/engine/runtime.js
index 10b2b5c8e9c..fa9ce2ed525 100644
--- a/src/engine/runtime.js
+++ b/src/engine/runtime.js
@@ -1533,6 +1533,13 @@ class Runtime extends EventEmitter {
blockJSON.mutator = blockInfo.mutator;
}
+ if (Object.prototype.hasOwnProperty.call(blockInfo, 'tooltip')) {
+ blockJSON.tooltip = blockInfo.tooltip;
+ }
+
+ if (Object.prototype.hasOwnProperty.call(blockInfo, 'warning')) {
+ blockJSON.warning = blockInfo.warning;
+ }
const blockText = Array.isArray(blockInfo.text) ? blockInfo.text : [blockInfo.text];
let inTextNum = 0; // text for the next block "arm" is blockText[inTextNum]
From baae53527d4bff9bd55ce1ef271197b29c42c109 Mon Sep 17 00:00:00 2001
From: Ashime Sho
Date: Fri, 20 Sep 2024 14:13:53 -0400
Subject: [PATCH 141/202] Export extensions to VM automatically
---
src/extension-support/extension-manager.js | 1 +
1 file changed, 1 insertion(+)
diff --git a/src/extension-support/extension-manager.js b/src/extension-support/extension-manager.js
index 236c41a3b32..b9a651615be 100644
--- a/src/extension-support/extension-manager.js
+++ b/src/extension-support/extension-manager.js
@@ -236,6 +236,7 @@ class ExtensionManager {
dispatch.setServiceSync(serviceName, extensionObject);
dispatch.callSync('extensions', 'registerExtensionServiceSync', serviceName);
this._loadedExtensions.set(extensionInfo.id, serviceName);
+ this.runtime[`cext_${extensionInfo.id}`] = extensionObject;
}
this._finishedLoadingExtensionScript();
From c9e38ce09e6dc140d637a4f2473dc938fa4fbda2 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E3=81=BF=E3=82=88?=
<135030944+yuri-kiss@users.noreply.github.com>
Date: Fri, 20 Sep 2024 14:55:32 -0400
Subject: [PATCH 142/202] Don't Cast objects to string (temporary fix)
---
src/engine/runtime.js | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/src/engine/runtime.js b/src/engine/runtime.js
index fa9ce2ed525..c283ec32170 100644
--- a/src/engine/runtime.js
+++ b/src/engine/runtime.js
@@ -3272,7 +3272,9 @@ class Runtime extends EventEmitter {
* @param {string} value Value to show associated with the block.
*/
visualReport (blockId, value) {
- this.emit(Runtime.VISUAL_REPORT, {id: blockId, value: String(value), type: typeof value});
+ this.emit(Runtime.VISUAL_REPORT, {id: blockId, value: ((
+ typeof value === 'object'
+ ) ? value : String(value)), type: typeof value});
}
/**
From 0e0f3442fc2b43647ec59af7a8266ea251f1303b Mon Sep 17 00:00:00 2001
From: Ashime Sho
Date: Fri, 20 Sep 2024 19:49:11 -0400
Subject: [PATCH 143/202] bro actually commited wrong branch push
---
src/engine/runtime.js | 44 +++++++++++++++++++++++++++++++++++++++++--
1 file changed, 42 insertions(+), 2 deletions(-)
diff --git a/src/engine/runtime.js b/src/engine/runtime.js
index c283ec32170..afa84130147 100644
--- a/src/engine/runtime.js
+++ b/src/engine/runtime.js
@@ -863,13 +863,21 @@ class Runtime extends EventEmitter {
}
/**
- * Event name for reporting that an extension as asked for a custom field to be added
+ * Event name for reporting that an extension has asked for a custom field to be added
* @const {string}
*/
static get EXTENSION_FIELD_ADDED () {
return 'EXTENSION_FIELD_ADDED';
}
+ /**
+ * Event name for reporting that an extension has asked for a custom shape type to be added
+ * @const {string}
+ */
+ static get EXTENSION_SHAPE_ADDED () {
+ return 'EXTENSION_SHAPE_TYPE_ADDED';
+ }
+
/**
* Event name for updating the available set of peripheral devices.
* This causes the peripheral connection modal to update a list of
@@ -1155,6 +1163,17 @@ class Runtime extends EventEmitter {
}
}
+ for (const blockShapeName in categoryInfo.customShapes) {
+ if (Object.prototype.hasOwnProperty.call(extensionInfo.customShapes, blockShapeName)) {
+ const blockShapeInfo = categoryInfo.customShapes[blockShapeName];
+
+ // Emit events for custom shape types from extension
+ this.emit(Runtime.EXTENSION_SHAPE_ADDED, {
+ implementation: blockShapeInfo,
+ });
+ }
+ }
+
this.emit(Runtime.EXTENSION_ADDED, categoryInfo);
}
@@ -1183,6 +1202,7 @@ class Runtime extends EventEmitter {
_fillExtensionCategory (categoryInfo, extensionInfo) {
categoryInfo.blocks = [];
categoryInfo.customFieldTypes = {};
+ categoryInfo.customShapes = {};
categoryInfo.menus = [];
categoryInfo.menuInfo = {};
categoryInfo.convertedMenuInfo = {};
@@ -1215,6 +1235,12 @@ class Runtime extends EventEmitter {
categoryInfo.customFieldTypes[fieldTypeName] = fieldTypeInfo;
}
}
+ for (const blockShapeName in extensionInfo.customShapes) {
+ if (Object.prototype.hasOwnProperty.call(extensionInfo.customShapes, blockShapeName)) {
+ const shapeType = extensionInfo.customShapes[blockShapeName];
+ categoryInfo.customShapes[blockShapeName] = shapeType;
+ }
+ }
if (extensionInfo.docsURI) {
const xml = '