diff --git a/src/lib/Toolbox/Toolbox.xml b/src/lib/Toolbox/Toolbox.xml
index c2327e2..06fc358 100644
--- a/src/lib/Toolbox/Toolbox.xml
+++ b/src/lib/Toolbox/Toolbox.xml
@@ -114,6 +114,31 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/patches/blockly/renderer.js b/src/patches/blockly/renderer.js
index a491ee5..4bfa9e1 100644
--- a/src/patches/blockly/renderer.js
+++ b/src/patches/blockly/renderer.js
@@ -18,6 +18,47 @@ class CustomConstantProvider extends Blockly.zelos.ConstantProvider {
this.ADD_START_HATS = true
this.PLUS = this.makePlus()
+ this.ARROW = this.makeArrow();
+ }
+
+ /** @returns {import("blockly/core/renderers/common/constants").Shape} */
+ makeArrow() {
+ const maxWidth = this.MAX_DYNAMIC_CONNECTION_SHAPE_WIDTH;
+ // try to get rid of the VERY noticeable gap
+ function makeLeftPath(height, up) {
+ const halfHeight = height / 2;
+ const width = halfHeight > maxWidth ? maxWidth : halfHeight;
+ const forward = up ? -1 : 1;
+ const direction = 1;
+ const dy = (forward * height) / 2;
+ return (
+ svgPaths.lineOnAxis("h", -width) +
+ svgPaths.lineOnAxis("v", dy * 2)
+ //svgPaths.lineTo(-width, up ? (forward * height) : 0) +
+ //svgPaths.lineOnAxis("v", dy)
+ );
+ }
+ return {
+ type: this.SHAPES.HEXAGONAL,
+ isDynamic: true,
+ width(height) {
+ const halfHeight = height / 2;
+ return halfHeight > maxWidth ? maxWidth : halfHeight
+ },
+ height(height) {
+ return height;
+ },
+ connectionOffsetY(connectionHeight) {
+ return connectionHeight / 2;
+ },
+ connectionOffsetX(connectionWidth) {
+ return -connectionWidth;
+ },
+ pathDown: (height) => makeLeftPath(height, false), //this.SQUARED.pathDown,
+ pathUp: (height) => makeLeftPath(height, true), //this.SQUARED.pathUp,
+ pathRightDown: this.HEXAGONAL.pathRightDown,
+ pathRightUp: this.HEXAGONAL.pathRightUp
+ }
}
/** @returns {import("blockly/core/renderers/common/constants").Shape} */
@@ -103,6 +144,8 @@ class CustomConstantProvider extends Blockly.zelos.ConstantProvider {
if (connection.type == Blockly.ConnectionType.INPUT_VALUE || connection.type == Blockly.ConnectionType.OUTPUT_VALUE) {
if (checks && checks.length > 1) {
return this.ROUNDED;
+ } else if (checks && checks.includes('Sprite')) {
+ return this.ARROW;
} else if (checks && checks.includes('List')) {
return this.PLUS;
} else if (checks && checks.includes('String')) {
diff --git a/src/resources/blocks/index.js b/src/resources/blocks/index.js
index 9466bbf..b26ca7c 100644
--- a/src/resources/blocks/index.js
+++ b/src/resources/blocks/index.js
@@ -6,6 +6,7 @@ import registerStrings from "./strings";
import registerInputs from "./inputs";
import registerVariables from "./variables";
import registerLists from "./lists";
+import registerSprites from "./sprites";
import registerBlocks from "./blocks";
import registerRuntime from "./runtime";
@@ -20,6 +21,7 @@ export default () => {
registerInputs();
registerVariables();
registerLists();
+ registerSprites();
registerBlocks();
registerRuntime();
diff --git a/src/resources/blocks/lists.js b/src/resources/blocks/lists.js
index 825069d..e3e14d5 100644
--- a/src/resources/blocks/lists.js
+++ b/src/resources/blocks/lists.js
@@ -96,7 +96,7 @@ function register() {
}, (block) => {
const INDEX = javascriptGenerator.valueToCode(block, 'INDEX')
const INPUT = javascriptGenerator.valueToCode(block, 'INPUT')
- const VALUE = javascriptGenerator.valueToCode(block, 'VALUE')
+ const VALUE = javascriptGenerator.valueToCode(block, 'TEXT')
const code = `ExtForge.Utils.setList(${INPUT}, ${INDEX}, ${VALUE})`;
return [`${code}`, 0];
})
diff --git a/src/resources/blocks/sprites.js b/src/resources/blocks/sprites.js
new file mode 100644
index 0000000..726e4e0
--- /dev/null
+++ b/src/resources/blocks/sprites.js
@@ -0,0 +1,319 @@
+import javascriptGenerator from '../javascriptGenerator';
+import { registerBlock } from '../register';
+import util from "../util";
+
+const categoryPrefix = 'sprites_';
+const categoryColor = '#18f';
+
+function register() {
+ registerBlock(`${categoryPrefix}stage`, {
+ message0: 'stage sprite',
+ args0: [],
+ output: "Sprite",
+ inputsInline: true,
+ colour: categoryColor
+ }, (block) => {
+ return ["Scratch.vm.runtime.getTargetForStage()", 0];
+ })
+ registerBlock(`${categoryPrefix}getSpriteFromName`, {
+ message0: "sprite named %1",
+ args0: [
+ {
+ "type": "field_input",
+ "name": "NAME",
+ "check": "String",
+ "text": "Sprite1",
+ "acceptsBlocks": true
+ }
+ ],
+ output: "Sprite",
+ inputsInline: true,
+ colour: categoryColor
+ }, (block) => {
+ const INPUT = javascriptGenerator.valueToCode(block, 'NAME');
+ return [`(()=>{const name = String(${INPUT}); const res = Scratch.vm.runtime.getSpriteTargetByName(name); if (!res) throw "Unable to find sprite named " + name; return res;})()`, 0]
+ })
+ registerBlock(`${categoryPrefix}getSpriteThatRanBlock`, {
+ message0: "sprite that ran this block",
+ args0: [],
+ output: "Sprite",
+ inputsInline: true,
+ colour: categoryColor
+ }, (block) => {
+ return [`(()=>{if(typeof blockUtils === "undefined")throw "This only works inside blocks ran by sprites";return blockUtils.target})()`, 0]
+ })
+ registerBlock(`${categoryPrefix}getListOfSprites`, {
+ message0: "get list of all sprites/clones",
+ args0: [],
+ output: "List",
+ inputsInline: true,
+ colour: categoryColor
+ }, (block) => {
+ return [`(new Proxy(Scratch.vm.runtime.executableTargets, {set: ()=>{throw "The list of all sprites is not mutable";}}))`, 0]
+ })
+ registerBlock(`${categoryPrefix}getClonesOfSprite`, {
+ message0: "clones of %1 (including itself)",
+ args0: [
+ {
+ "type": "input_value",
+ "name": "SPRITE",
+ "check": "Sprite",
+ }
+ ],
+ output: "List",
+ inputsInline: true,
+ colour: categoryColor
+ }, (block) => {
+ const SPRITE = javascriptGenerator.valueToCode(block, 'SPRITE') || "ExtForge.Utils.throw('Sprite inputs MUST have a sprite inside them')";
+ return [`(new Proxy(${SPRITE}.sprite.clones, {set: ()=>{throw "The list of clones of a sprite is not mutable";}}))`, 0]
+ })
+ registerBlock(`${categoryPrefix}getNameOfSprite`, {
+ message0: "get name of %1",
+ args0: [
+ {
+ "type": "input_value",
+ "name": "SPRITE",
+ "check": "Sprite",
+ }
+ ],
+ output: "Number",
+ inputsInline: true,
+ colour: categoryColor
+ }, (block) => {
+ const SPRITE = javascriptGenerator.valueToCode(block, 'SPRITE') || "ExtForge.Utils.throw('Sprite inputs MUST have a sprite inside them')";
+ return [`${SPRITE}.getName()`, 0]
+ })
+ registerBlock(`${categoryPrefix}getIdOfSprite`, {
+ message0: "get id of %1",
+ args0: [
+ {
+ "type": "input_value",
+ "name": "SPRITE",
+ "check": "Sprite",
+ }
+ ],
+ output: "Number",
+ inputsInline: true,
+ colour: categoryColor
+ }, (block) => {
+ const SPRITE = javascriptGenerator.valueToCode(block, 'SPRITE') || "ExtForge.Utils.throw('Sprite inputs MUST have a sprite inside them')";
+ return [`${SPRITE}.id`, 0]
+ })
+ registerBlock(`${categoryPrefix}getXposOfSprite`, {
+ message0: "get x position of %1",
+ args0: [
+ {
+ "type": "input_value",
+ "name": "SPRITE",
+ "check": "Sprite",
+ }
+ ],
+ output: "Number",
+ inputsInline: true,
+ colour: categoryColor
+ }, (block) => {
+ const SPRITE = javascriptGenerator.valueToCode(block, 'SPRITE') || "ExtForge.Utils.throw('Sprite inputs MUST have a sprite inside them')";
+ return [`${SPRITE}.x`, 0]
+ })
+ registerBlock(`${categoryPrefix}getYposOfSprite`, {
+ message0: "get y position of %1",
+ args0: [
+ {
+ "type": "input_value",
+ "name": "SPRITE",
+ "check": "Sprite",
+ }
+ ],
+ output: "Number",
+ inputsInline: true,
+ colour: categoryColor
+ }, (block) => {
+ const SPRITE = javascriptGenerator.valueToCode(block, 'SPRITE') || "ExtForge.Utils.throw('Sprite inputs MUST have a sprite inside them')";
+ return [`${SPRITE}.y`, 0]
+ })
+ registerBlock(`${categoryPrefix}getDirectionOfSprite`, {
+ message0: "get direction of %1",
+ args0: [
+ {
+ "type": "input_value",
+ "name": "SPRITE",
+ "check": "Sprite",
+ }
+ ],
+ output: "Number",
+ inputsInline: true,
+ colour: categoryColor
+ }, (block) => {
+ const SPRITE = javascriptGenerator.valueToCode(block, 'SPRITE') || "ExtForge.Utils.throw('Sprite inputs MUST have a sprite inside them')";
+ return [`${SPRITE}.direction`, 0]
+ })
+ registerBlock(`${categoryPrefix}setXposOfSprite`, {
+ message0: 'set x position of %1 to %2',
+ args0: [
+ {
+ "type": "input_value",
+ "name": "SPRITE",
+ "check": "Sprite",
+ },
+ {
+ "type": "field_number",
+ "name": "INPUT",
+ "check": null,
+ "text": "0",
+ "acceptsBlocks": true
+ }
+ ],
+ previousStatement: null,
+ nextStatement: null,
+ inputsInline: true,
+ colour: categoryColor
+ }, (block) => {
+ const SPRITE = javascriptGenerator.valueToCode(block, 'SPRITE') || "ExtForge.Utils.throw('Sprite inputs MUST have a sprite inside them')";
+ const VALUE = javascriptGenerator.valueToCode(block, 'INPUT');
+ const code = `((spriteVariable)=>spriteVariable.setXY(Scratch.Cast.toNumber(${VALUE}), spriteVariable.y))(${SPRITE})`;
+ return `${code}\n`;
+ })
+ registerBlock(`${categoryPrefix}setYposOfSprite`, {
+ message0: 'set y position of %1 to %2',
+ args0: [
+ {
+ "type": "input_value",
+ "name": "SPRITE",
+ "check": "Sprite",
+ },
+ {
+ "type": "field_number",
+ "name": "INPUT",
+ "check": null,
+ "text": "0",
+ "acceptsBlocks": true
+ }
+ ],
+ previousStatement: null,
+ nextStatement: null,
+ inputsInline: true,
+ colour: categoryColor
+ }, (block) => {
+ const SPRITE = javascriptGenerator.valueToCode(block, 'SPRITE') || "ExtForge.Utils.throw('Sprite inputs MUST have a sprite inside them')";
+ const VALUE = javascriptGenerator.valueToCode(block, 'INPUT');
+ const code = `((spriteVariable)=>spriteVariable.setXY(spriteVariable.x, Scratch.Cast.toNumber(${VALUE})))(${SPRITE})`;
+ return `${code}\n`;
+ })
+ registerBlock(`${categoryPrefix}setDirectionOfSprite`, {
+ message0: 'set direction of %1 to %2',
+ args0: [
+ {
+ "type": "input_value",
+ "name": "SPRITE",
+ "check": "Sprite",
+ },
+ {
+ "type": "field_number",
+ "name": "INPUT",
+ "check": null,
+ "text": "90",
+ "acceptsBlocks": true
+ }
+ ],
+ previousStatement: null,
+ nextStatement: null,
+ inputsInline: true,
+ colour: categoryColor
+ }, (block) => {
+ const SPRITE = javascriptGenerator.valueToCode(block, 'SPRITE') || "ExtForge.Utils.throw('Sprite inputs MUST have a sprite inside them')";
+ const VALUE = javascriptGenerator.valueToCode(block, 'INPUT');
+ const code = `((spriteVariable)=>spriteVariable.setDirection(Scratch.Cast.toNumber(${VALUE})))(${SPRITE})`;
+ return `${code}\n`;
+ })
+ registerBlock(`${categoryPrefix}hasSpriteBeenDeleted`, {
+ message0: "has %1 been deleted?",
+ args0: [
+ {
+ "type": "input_value",
+ "name": "SPRITE",
+ "check": "Sprite",
+ }
+ ],
+ output: "Boolean",
+ inputsInline: true,
+ colour: categoryColor
+ }, (block) => {
+ const SPRITE = javascriptGenerator.valueToCode(block, 'SPRITE') || "ExtForge.Utils.throw('Sprite inputs MUST have a sprite inside them')";
+ return [`(Scratch.vm.runtime.executableTargets.indexOf(${SPRITE}) !== -1)`, 0]
+ })
+ registerBlock(`${categoryPrefix}isSpriteAClone`, {
+ message0: "is %1 a clone?",
+ args0: [
+ {
+ "type": "input_value",
+ "name": "SPRITE",
+ "check": "Sprite",
+ }
+ ],
+ output: "Boolean",
+ inputsInline: true,
+ colour: categoryColor
+ }, (block) => {
+ const SPRITE = javascriptGenerator.valueToCode(block, 'SPRITE') || "ExtForge.Utils.throw('Sprite inputs MUST have a sprite inside them')";
+ return [`!(${SPRITE}.isOriginal)`, 0]
+ })
+ registerBlock(`${categoryPrefix}getVarOfSprite`, {
+ message0: 'get variable %1 of %2',
+ args0: [
+ {
+ "type": "field_input",
+ "name": "INPUT",
+ "check": null,
+ "text": "my sprite-only variable",
+ "acceptsBlocks": true
+ },
+ {
+ "type": "input_value",
+ "name": "SPRITE",
+ "check": "Sprite",
+ },
+ ],
+ output: null,
+ inputsInline: true,
+ colour: categoryColor
+ }, (block) => {
+ const SPRITE = javascriptGenerator.valueToCode(block, 'SPRITE') || "ExtForge.Utils.throw('Sprite inputs MUST have a sprite inside them')";
+ const VALUE = javascriptGenerator.valueToCode(block, 'INPUT');
+ return [`((${SPRITE}.lookupVariableByNameAndType(String(${VALUE})) || {value: 0}).value)`, 0]
+ })
+ registerBlock(`${categoryPrefix}setVarOfSprite`, {
+ message0: 'set variable %1 of %2 to %3',
+ args0: [
+ {
+ "type": "field_input",
+ "name": "INPUT",
+ "check": null,
+ "text": "my sprite-only variable",
+ "acceptsBlocks": true
+ },
+ {
+ "type": "input_value",
+ "name": "SPRITE",
+ "check": "Sprite",
+ },
+ {
+ "type": "field_input",
+ "name": "VALUE",
+ "check": null,
+ "text": "0",
+ "acceptsBlocks": true
+ },
+ ],
+ previousStatement: null,
+ nextStatement: null,
+ inputsInline: true,
+ colour: categoryColor
+ }, (block) => {
+ const SPRITE = javascriptGenerator.valueToCode(block, 'SPRITE') || "ExtForge.Utils.throw('Sprite inputs MUST have a sprite inside them')";
+ const INPUT = javascriptGenerator.valueToCode(block, 'INPUT');
+ const VALUE = javascriptGenerator.valueToCode(block, 'VALUE')
+ return `((${SPRITE}.lookupVariableByNameAndType(String(${INPUT})) || {value: 0}).value = String(${VALUE}))`
+ })
+}
+
+export default register;
\ No newline at end of file
diff --git a/src/resources/compiler/index.js b/src/resources/compiler/index.js
index 8eb8ba1..cc8c261 100644
--- a/src/resources/compiler/index.js
+++ b/src/resources/compiler/index.js
@@ -41,6 +41,9 @@ const ExtForge = {
},
countString: (x, y) => {
return y.length == 0 ? 0 : x.split(y).length - 1
+ },
+ throw: function(err) {
+ throw err;
}
}
}
@@ -135,7 +138,7 @@ class Compiler {
workspace.getTopBlocks().find(v => v.type == "blocks_define" && v.blockId_ == id),
"BLOCKS"
)
- return `async block_${id}(args) { ${blockCode} }`
+ return `async block_${id}(args, blockUtils) { ${blockCode} }`
}), classRegistry.bottom, code, footerCode).join('\n');
}
}