diff --git a/src/blocks/scratch3_event.js b/src/blocks/scratch3_event.js index 5ee2fab1d..1964ccf9f 100644 --- a/src/blocks/scratch3_event.js +++ b/src/blocks/scratch3_event.js @@ -32,6 +32,9 @@ class Scratch3EventBlocks { event_whenthisspriteclicked: { restartExistingThreads: true }, + event_whenstageclicked: { + restartExistingThreads: true + }, event_whenbackdropswitchesto: { restartExistingThreads: true }, diff --git a/src/engine/blocks.js b/src/engine/blocks.js index 1e22bf73e..baad5e4c6 100644 --- a/src/engine/blocks.js +++ b/src/engine/blocks.js @@ -593,6 +593,21 @@ class Blocks { } } + /** + * Keep blocks up to date after they are shared between targets. + * @param {boolean} isStage If the new target is a stage. + */ + updateTargetSpecificBlocks (isStage) { + const blocks = this._blocks; + for (const blockId in blocks) { + if (isStage && blocks[blockId].opcode === 'event_whenthisspriteclicked') { + blocks[blockId].opcode = 'event_whenstageclicked'; + } else if (!isStage && blocks[blockId].opcode === 'event_whenstageclicked') { + blocks[blockId].opcode = 'event_whenthisspriteclicked'; + } + } + } + /** * Update blocks after a sound, costume, or backdrop gets renamed. * Any block referring to the old name of the asset should get updated diff --git a/src/io/mouse.js b/src/io/mouse.js index eef3c4c86..2c4aa8aee 100644 --- a/src/io/mouse.js +++ b/src/io/mouse.js @@ -31,8 +31,15 @@ class Mouse { // only activate click hat if the mouse up event wasn't // the result of a drag ending if (!wasDragged) { + // Activate both "this sprite clicked" and "stage clicked" + // They were separated into two opcodes for labeling, + // but should act the same way. + // Intentionally not checking isStage to make it work when sharing blocks. + // @todo the blocks should be converted from one to another when shared this.runtime.startHats('event_whenthisspriteclicked', null, target); + this.runtime.startHats('event_whenstageclicked', + null, target); } return; } diff --git a/src/serialization/sb2.js b/src/serialization/sb2.js index bf9b74933..27c1412d4 100644 --- a/src/serialization/sb2.js +++ b/src/serialization/sb2.js @@ -275,6 +275,9 @@ const parseScratchObject = function (object, runtime, extensions, topLevel) { parseScripts(object.scripts, blocks, addBroadcastMsg, getVariableId, extensions); } + // Update stage specific blocks (e.g. sprite clicked <=> stage clicked) + blocks.updateTargetSpecificBlocks(topLevel); // topLevel = isStage + if (object.hasOwnProperty('lists')) { for (let k = 0; k < object.lists.length; k++) { const list = object.lists[k]; diff --git a/src/virtual-machine.js b/src/virtual-machine.js index da9849616..e90a548ca 100644 --- a/src/virtual-machine.js +++ b/src/virtual-machine.js @@ -778,6 +778,7 @@ class VirtualMachine extends EventEmitter { for (let i = 0; i < blocks.length; i++) { target.blocks.createBlock(blocks[i]); } + target.blocks.updateTargetSpecificBlocks(target.isStage); } /** diff --git a/test/fixtures/when-clicked.sb2 b/test/fixtures/when-clicked.sb2 new file mode 100644 index 000000000..e5d789d8d Binary files /dev/null and b/test/fixtures/when-clicked.sb2 differ diff --git a/test/unit/engine_blocks.js b/test/unit/engine_blocks.js index 3cb090a88..08b0b414e 100644 --- a/test/unit/engine_blocks.js +++ b/test/unit/engine_blocks.js @@ -746,3 +746,33 @@ test('updateAssetName doesn\'t update name if name isn\'t being used', t => { t.equals(b.getBlock('id1').fields.BACKDROP.value, 'foo'); t.end(); }); + +test('updateTargetSpecificBlocks changes sprite clicked hat to stage clicked for stage', t => { + const b = new Blocks(); + b.createBlock({ + id: 'originallySpriteClicked', + opcode: 'event_whenthisspriteclicked' + }); + b.createBlock({ + id: 'originallyStageClicked', + opcode: 'event_whenstageclicked' + }); + + // originallySpriteClicked does not update when on a non-stage target + b.updateTargetSpecificBlocks(false /* isStage */); + t.equals(b.getBlock('originallySpriteClicked').opcode, 'event_whenthisspriteclicked'); + + // originallySpriteClicked does update when on a stage target + b.updateTargetSpecificBlocks(true /* isStage */); + t.equals(b.getBlock('originallySpriteClicked').opcode, 'event_whenstageclicked'); + + // originallyStageClicked does not update when on a stage target + b.updateTargetSpecificBlocks(true /* isStage */); + t.equals(b.getBlock('originallyStageClicked').opcode, 'event_whenstageclicked'); + + // originallyStageClicked does update when on a non-stage target + b.updateTargetSpecificBlocks(false/* isStage */); + t.equals(b.getBlock('originallyStageClicked').opcode, 'event_whenthisspriteclicked'); + + t.end(); +}); diff --git a/test/unit/serialization_sb2.js b/test/unit/serialization_sb2.js index d5aaa0248..933baa3e0 100644 --- a/test/unit/serialization_sb2.js +++ b/test/unit/serialization_sb2.js @@ -68,3 +68,26 @@ test('data scoping', t => { t.end(); }); }); + +test('whenclicked blocks imported separately', t => { + // This sb2 fixture has a single "whenClicked" block on both sprite and stage + const uri = path.resolve(__dirname, '../fixtures/when-clicked.sb2'); + const file = extract(uri); + const json = JSON.parse(file); + + // Create runtime instance & load SB2 into it + const rt = new Runtime(); + sb2.deserialize(json, rt).then(({targets}) => { + const stage = targets[0]; + t.equal(stage.isStage, true); // Make sure we have the correct target + const stageOpcode = stage.blocks.getBlock(stage.blocks.getScripts()[0]).opcode; + t.equal(stageOpcode, 'event_whenstageclicked'); + + const sprite = targets[1]; + t.equal(sprite.isStage, false); // Make sure we have the correct target + const spriteOpcode = sprite.blocks.getBlock(sprite.blocks.getScripts()[0]).opcode; + t.equal(spriteOpcode, 'event_whenthisspriteclicked'); + + t.end(); + }); +});