@scratch/scratch-vm 11.0.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.jsdoc.json +20 -0
- package/.nvmrc +1 -0
- package/CHANGELOG.md +7004 -0
- package/LICENSE +12 -0
- package/README.md +120 -0
- package/TRADEMARK +1 -0
- package/commitlint.config.js +4 -0
- package/docs/extensions.md +527 -0
- package/package.json +103 -0
- package/release.config.js +13 -0
- package/src/blocks/scratch3_control.js +206 -0
- package/src/blocks/scratch3_core_example.js +69 -0
- package/src/blocks/scratch3_data.js +257 -0
- package/src/blocks/scratch3_event.js +137 -0
- package/src/blocks/scratch3_looks.js +612 -0
- package/src/blocks/scratch3_motion.js +288 -0
- package/src/blocks/scratch3_operators.js +154 -0
- package/src/blocks/scratch3_procedures.js +79 -0
- package/src/blocks/scratch3_sensing.js +336 -0
- package/src/blocks/scratch3_sound.js +350 -0
- package/src/dispatch/central-dispatch.js +143 -0
- package/src/dispatch/shared-dispatch.js +235 -0
- package/src/dispatch/worker-dispatch.js +110 -0
- package/src/engine/adapter.js +176 -0
- package/src/engine/block-utility.js +242 -0
- package/src/engine/blocks-execute-cache.js +19 -0
- package/src/engine/blocks-runtime-cache.js +78 -0
- package/src/engine/blocks.js +1299 -0
- package/src/engine/comment.js +56 -0
- package/src/engine/execute.js +580 -0
- package/src/engine/monitor-record.js +23 -0
- package/src/engine/mutation-adapter.js +48 -0
- package/src/engine/profiler.js +390 -0
- package/src/engine/runtime.js +2686 -0
- package/src/engine/scratch-blocks-constants.js +27 -0
- package/src/engine/sequencer.js +361 -0
- package/src/engine/stage-layering.js +29 -0
- package/src/engine/target.js +799 -0
- package/src/engine/thread.js +404 -0
- package/src/engine/variable.js +70 -0
- package/src/extension-support/argument-type.js +47 -0
- package/src/extension-support/block-type.js +50 -0
- package/src/extension-support/define-messages.js +18 -0
- package/src/extension-support/extension-manager.js +440 -0
- package/src/extension-support/extension-metadata.js +64 -0
- package/src/extension-support/extension-worker.js +59 -0
- package/src/extension-support/reporter-scope.js +18 -0
- package/src/extension-support/target-type.js +17 -0
- package/src/extensions/scratch3_boost/index.js +2113 -0
- package/src/extensions/scratch3_ev3/index.js +1355 -0
- package/src/extensions/scratch3_gdx_for/index.js +981 -0
- package/src/extensions/scratch3_gdx_for/scratch-link-device-adapter.js +44 -0
- package/src/extensions/scratch3_makeymakey/index.js +396 -0
- package/src/extensions/scratch3_microbit/index.js +984 -0
- package/src/extensions/scratch3_music/assets/drums/1-snare.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/drums/10-wood-block.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/drums/11-cowbell.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/drums/12-triangle.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/drums/13-bongo.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/drums/14-conga.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/drums/15-cabasa.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/drums/16-guiro.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/drums/17-vibraslap.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/drums/18-cuica.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/drums/2-bass-drum.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/drums/3-side-stick.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/drums/4-crash-cymbal.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/drums/5-open-hi-hat.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/drums/6-closed-hi-hat.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/drums/7-tambourine.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/drums/8-hand-clap.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/drums/9-claves.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/1-piano/108.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/1-piano/24.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/1-piano/36.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/1-piano/48.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/1-piano/60.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/1-piano/72.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/1-piano/84.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/1-piano/96.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/10-clarinet/48.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/10-clarinet/60.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/11-saxophone/36.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/11-saxophone/60.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/11-saxophone/84.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/12-flute/60.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/12-flute/72.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/13-wooden-flute/60.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/13-wooden-flute/72.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/14-bassoon/36.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/14-bassoon/48.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/14-bassoon/60.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/15-choir/48.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/15-choir/60.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/15-choir/72.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/16-vibraphone/60.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/16-vibraphone/72.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/17-music-box/60.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/18-steel-drum/60.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/19-marimba/60.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/2-electric-piano/60.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/20-synth-lead/60.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/21-synth-pad/60.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/3-organ/60.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/4-guitar/60.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/5-electric-guitar/60.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/6-bass/36.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/6-bass/48.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/7-pizzicato/60.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/8-cello/36.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/8-cello/48.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/8-cello/60.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/9-trombone/36.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/9-trombone/48.mp3 +0 -0
- package/src/extensions/scratch3_music/assets/instruments/9-trombone/60.mp3 +0 -0
- package/src/extensions/scratch3_music/index.js +1333 -0
- package/src/extensions/scratch3_music/manifest.js +63 -0
- package/src/extensions/scratch3_pen/index.js +770 -0
- package/src/extensions/scratch3_speech2text/index.js +700 -0
- package/src/extensions/scratch3_text2speech/index.js +766 -0
- package/src/extensions/scratch3_translate/index.js +286 -0
- package/src/extensions/scratch3_video_sensing/debug.js +13 -0
- package/src/extensions/scratch3_video_sensing/index.js +594 -0
- package/src/extensions/scratch3_video_sensing/library.js +384 -0
- package/src/extensions/scratch3_video_sensing/math.js +76 -0
- package/src/extensions/scratch3_video_sensing/view.js +509 -0
- package/src/extensions/scratch3_wedo2/index.js +1616 -0
- package/src/import/load-costume.js +416 -0
- package/src/import/load-sound.js +116 -0
- package/src/index.js +10 -0
- package/src/io/ble.js +256 -0
- package/src/io/bt.js +202 -0
- package/src/io/clock.js +39 -0
- package/src/io/cloud.js +169 -0
- package/src/io/keyboard.js +146 -0
- package/src/io/mouse.js +145 -0
- package/src/io/mouseWheel.js +28 -0
- package/src/io/userData.js +24 -0
- package/src/io/video.js +210 -0
- package/src/playground/benchmark.css +83 -0
- package/src/playground/benchmark.js +753 -0
- package/src/playground/index.html +111 -0
- package/src/playground/suite.css +78 -0
- package/src/playground/suite.html +25 -0
- package/src/playground/suite.js +544 -0
- package/src/playground/video-sensing.html +18 -0
- package/src/playground/video-sensing.js +133 -0
- package/src/serialization/deserialize-assets.js +178 -0
- package/src/serialization/sb2.js +1295 -0
- package/src/serialization/sb2_specmap.js +1818 -0
- package/src/serialization/sb3.js +1320 -0
- package/src/serialization/serialize-assets.js +60 -0
- package/src/sprites/rendered-target.js +1115 -0
- package/src/sprites/sprite.js +177 -0
- package/src/util/base64-util.js +48 -0
- package/src/util/cast.js +218 -0
- package/src/util/clone.js +17 -0
- package/src/util/color.js +204 -0
- package/src/util/fetch-with-timeout.js +53 -0
- package/src/util/get-monitor-id.js +33 -0
- package/src/util/jsonrpc.js +114 -0
- package/src/util/log.js +4 -0
- package/src/util/math-util.js +122 -0
- package/src/util/maybe-format-message.js +18 -0
- package/src/util/new-block-ids.js +33 -0
- package/src/util/rateLimiter.js +73 -0
- package/src/util/scratch-link-websocket.js +133 -0
- package/src/util/string-util.js +94 -0
- package/src/util/task-queue.js +203 -0
- package/src/util/timer.js +103 -0
- package/src/util/uid.js +29 -0
- package/src/util/variable-util.js +48 -0
- package/src/util/xml-escape.js +33 -0
- package/src/virtual-machine.js +1586 -0
- package/webpack.config.js +134 -0
|
@@ -0,0 +1,612 @@
|
|
|
1
|
+
const Cast = require('../util/cast');
|
|
2
|
+
const Clone = require('../util/clone');
|
|
3
|
+
const RenderedTarget = require('../sprites/rendered-target');
|
|
4
|
+
const uid = require('../util/uid');
|
|
5
|
+
const StageLayering = require('../engine/stage-layering');
|
|
6
|
+
const getMonitorIdForBlockWithArgs = require('../util/get-monitor-id');
|
|
7
|
+
const MathUtil = require('../util/math-util');
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @typedef {object} BubbleState - the bubble state associated with a particular target.
|
|
11
|
+
* @property {Boolean} onSpriteRight - tracks whether the bubble is right or left of the sprite.
|
|
12
|
+
* @property {?int} drawableId - the ID of the associated bubble Drawable, null if none.
|
|
13
|
+
* @property {string} text - the text of the bubble.
|
|
14
|
+
* @property {string} type - the type of the bubble, "say" or "think"
|
|
15
|
+
* @property {?string} usageId - ID indicating the most recent usage of the say/think bubble.
|
|
16
|
+
* Used for comparison when determining whether to clear a say/think bubble.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
class Scratch3LooksBlocks {
|
|
20
|
+
constructor (runtime) {
|
|
21
|
+
/**
|
|
22
|
+
* The runtime instantiating this block package.
|
|
23
|
+
* @type {Runtime}
|
|
24
|
+
*/
|
|
25
|
+
this.runtime = runtime;
|
|
26
|
+
|
|
27
|
+
this._onTargetChanged = this._onTargetChanged.bind(this);
|
|
28
|
+
this._onResetBubbles = this._onResetBubbles.bind(this);
|
|
29
|
+
this._onTargetWillExit = this._onTargetWillExit.bind(this);
|
|
30
|
+
this._updateBubble = this._updateBubble.bind(this);
|
|
31
|
+
|
|
32
|
+
// Reset all bubbles on start/stop
|
|
33
|
+
this.runtime.on('PROJECT_STOP_ALL', this._onResetBubbles);
|
|
34
|
+
this.runtime.on('targetWasRemoved', this._onTargetWillExit);
|
|
35
|
+
|
|
36
|
+
// Enable other blocks to use bubbles like ask/answer
|
|
37
|
+
this.runtime.on(Scratch3LooksBlocks.SAY_OR_THINK, this._updateBubble);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* The default bubble state, to be used when a target has no existing bubble state.
|
|
42
|
+
* @type {BubbleState}
|
|
43
|
+
*/
|
|
44
|
+
static get DEFAULT_BUBBLE_STATE () {
|
|
45
|
+
return {
|
|
46
|
+
drawableId: null,
|
|
47
|
+
onSpriteRight: true,
|
|
48
|
+
skinId: null,
|
|
49
|
+
text: '',
|
|
50
|
+
type: 'say',
|
|
51
|
+
usageId: null
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* The key to load & store a target's bubble-related state.
|
|
57
|
+
* @type {string}
|
|
58
|
+
*/
|
|
59
|
+
static get STATE_KEY () {
|
|
60
|
+
return 'Scratch.looks';
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Event name for a text bubble being created or updated.
|
|
65
|
+
* @const {string}
|
|
66
|
+
*/
|
|
67
|
+
static get SAY_OR_THINK () {
|
|
68
|
+
// There are currently many places in the codebase which explicitly refer to this event by the string 'SAY',
|
|
69
|
+
// so keep this as the string 'SAY' for now rather than changing it to 'SAY_OR_THINK' and breaking things.
|
|
70
|
+
return 'SAY';
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Limit for say bubble string.
|
|
75
|
+
* @const {string}
|
|
76
|
+
*/
|
|
77
|
+
static get SAY_BUBBLE_LIMIT () {
|
|
78
|
+
return 330;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Limit for ghost effect
|
|
83
|
+
* @const {object}
|
|
84
|
+
*/
|
|
85
|
+
static get EFFECT_GHOST_LIMIT (){
|
|
86
|
+
return {min: 0, max: 100};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Limit for brightness effect
|
|
91
|
+
* @const {object}
|
|
92
|
+
*/
|
|
93
|
+
static get EFFECT_BRIGHTNESS_LIMIT (){
|
|
94
|
+
return {min: -100, max: 100};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* @param {Target} target - collect bubble state for this target. Probably, but not necessarily, a RenderedTarget.
|
|
99
|
+
* @returns {BubbleState} the mutable bubble state associated with that target. This will be created if necessary.
|
|
100
|
+
* @private
|
|
101
|
+
*/
|
|
102
|
+
_getBubbleState (target) {
|
|
103
|
+
let bubbleState = target.getCustomState(Scratch3LooksBlocks.STATE_KEY);
|
|
104
|
+
if (!bubbleState) {
|
|
105
|
+
bubbleState = Clone.simple(Scratch3LooksBlocks.DEFAULT_BUBBLE_STATE);
|
|
106
|
+
target.setCustomState(Scratch3LooksBlocks.STATE_KEY, bubbleState);
|
|
107
|
+
}
|
|
108
|
+
return bubbleState;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Handle a target which has moved.
|
|
113
|
+
* @param {RenderedTarget} target - the target which has moved.
|
|
114
|
+
* @private
|
|
115
|
+
*/
|
|
116
|
+
_onTargetChanged (target) {
|
|
117
|
+
const bubbleState = this._getBubbleState(target);
|
|
118
|
+
if (bubbleState.drawableId) {
|
|
119
|
+
this._positionBubble(target);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Handle a target which is exiting.
|
|
125
|
+
* @param {RenderedTarget} target - the target.
|
|
126
|
+
* @private
|
|
127
|
+
*/
|
|
128
|
+
_onTargetWillExit (target) {
|
|
129
|
+
const bubbleState = this._getBubbleState(target);
|
|
130
|
+
if (bubbleState.drawableId && bubbleState.skinId) {
|
|
131
|
+
this.runtime.renderer.destroyDrawable(bubbleState.drawableId, StageLayering.SPRITE_LAYER);
|
|
132
|
+
this.runtime.renderer.destroySkin(bubbleState.skinId);
|
|
133
|
+
bubbleState.drawableId = null;
|
|
134
|
+
bubbleState.skinId = null;
|
|
135
|
+
this.runtime.requestRedraw();
|
|
136
|
+
}
|
|
137
|
+
target.removeListener(RenderedTarget.EVENT_TARGET_VISUAL_CHANGE, this._onTargetChanged);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Handle project start/stop by clearing all visible bubbles.
|
|
142
|
+
* @private
|
|
143
|
+
*/
|
|
144
|
+
_onResetBubbles () {
|
|
145
|
+
for (let n = 0; n < this.runtime.targets.length; n++) {
|
|
146
|
+
const bubbleState = this._getBubbleState(this.runtime.targets[n]);
|
|
147
|
+
bubbleState.text = '';
|
|
148
|
+
this._onTargetWillExit(this.runtime.targets[n]);
|
|
149
|
+
}
|
|
150
|
+
clearTimeout(this._bubbleTimeout);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Position the bubble of a target. If it doesn't fit on the specified side, flip and rerender.
|
|
155
|
+
* @param {!Target} target Target whose bubble needs positioning.
|
|
156
|
+
* @private
|
|
157
|
+
*/
|
|
158
|
+
_positionBubble (target) {
|
|
159
|
+
if (!target.visible) return;
|
|
160
|
+
const bubbleState = this._getBubbleState(target);
|
|
161
|
+
const [bubbleWidth, bubbleHeight] = this.runtime.renderer.getCurrentSkinSize(bubbleState.drawableId);
|
|
162
|
+
let targetBounds;
|
|
163
|
+
try {
|
|
164
|
+
targetBounds = target.getBoundsForBubble();
|
|
165
|
+
} catch (error_) {
|
|
166
|
+
// Bounds calculation could fail (e.g. on empty costumes), in that case
|
|
167
|
+
// use the x/y position of the target.
|
|
168
|
+
targetBounds = {
|
|
169
|
+
left: target.x,
|
|
170
|
+
right: target.x,
|
|
171
|
+
top: target.y,
|
|
172
|
+
bottom: target.y
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
const stageSize = this.runtime.renderer.getNativeSize();
|
|
176
|
+
const stageBounds = {
|
|
177
|
+
left: -stageSize[0] / 2,
|
|
178
|
+
right: stageSize[0] / 2,
|
|
179
|
+
top: stageSize[1] / 2,
|
|
180
|
+
bottom: -stageSize[1] / 2
|
|
181
|
+
};
|
|
182
|
+
if (bubbleState.onSpriteRight && bubbleWidth + targetBounds.right > stageBounds.right &&
|
|
183
|
+
(targetBounds.left - bubbleWidth > stageBounds.left)) { // Only flip if it would fit
|
|
184
|
+
bubbleState.onSpriteRight = false;
|
|
185
|
+
this._renderBubble(target);
|
|
186
|
+
} else if (!bubbleState.onSpriteRight && targetBounds.left - bubbleWidth < stageBounds.left &&
|
|
187
|
+
(bubbleWidth + targetBounds.right < stageBounds.right)) { // Only flip if it would fit
|
|
188
|
+
bubbleState.onSpriteRight = true;
|
|
189
|
+
this._renderBubble(target);
|
|
190
|
+
} else {
|
|
191
|
+
this.runtime.renderer.updateDrawablePosition(bubbleState.drawableId, [
|
|
192
|
+
bubbleState.onSpriteRight ? (
|
|
193
|
+
Math.max(
|
|
194
|
+
stageBounds.left, // Bubble should not extend past left edge of stage
|
|
195
|
+
Math.min(stageBounds.right - bubbleWidth, targetBounds.right)
|
|
196
|
+
)
|
|
197
|
+
) : (
|
|
198
|
+
Math.min(
|
|
199
|
+
stageBounds.right - bubbleWidth, // Bubble should not extend past right edge of stage
|
|
200
|
+
Math.max(stageBounds.left, targetBounds.left - bubbleWidth)
|
|
201
|
+
)
|
|
202
|
+
),
|
|
203
|
+
// Bubble should not extend past the top of the stage
|
|
204
|
+
Math.min(stageBounds.top, targetBounds.bottom + bubbleHeight)
|
|
205
|
+
]);
|
|
206
|
+
this.runtime.requestRedraw();
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Create a visible bubble for a target. If a bubble exists for the target,
|
|
212
|
+
* just set it to visible and update the type/text. Otherwise create a new
|
|
213
|
+
* bubble and update the relevant custom state.
|
|
214
|
+
* @param {!Target} target Target who needs a bubble.
|
|
215
|
+
* @return {undefined} Early return if text is empty string.
|
|
216
|
+
* @private
|
|
217
|
+
*/
|
|
218
|
+
_renderBubble (target) {
|
|
219
|
+
if (!this.runtime.renderer) return;
|
|
220
|
+
|
|
221
|
+
const bubbleState = this._getBubbleState(target);
|
|
222
|
+
const {type, text, onSpriteRight} = bubbleState;
|
|
223
|
+
|
|
224
|
+
// Remove the bubble if target is not visible, or text is being set to blank.
|
|
225
|
+
if (!target.visible || text === '') {
|
|
226
|
+
this._onTargetWillExit(target);
|
|
227
|
+
return;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
if (bubbleState.skinId) {
|
|
231
|
+
this.runtime.renderer.updateTextSkin(bubbleState.skinId, type, text, onSpriteRight, [0, 0]);
|
|
232
|
+
} else {
|
|
233
|
+
target.addListener(RenderedTarget.EVENT_TARGET_VISUAL_CHANGE, this._onTargetChanged);
|
|
234
|
+
bubbleState.drawableId = this.runtime.renderer.createDrawable(StageLayering.SPRITE_LAYER);
|
|
235
|
+
bubbleState.skinId = this.runtime.renderer.createTextSkin(type, text, bubbleState.onSpriteRight, [0, 0]);
|
|
236
|
+
this.runtime.renderer.updateDrawableSkinId(bubbleState.drawableId, bubbleState.skinId);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
this._positionBubble(target);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Properly format text for a text bubble.
|
|
244
|
+
* @param {string} text The text to be formatted
|
|
245
|
+
* @return {string} The formatted text
|
|
246
|
+
* @private
|
|
247
|
+
*/
|
|
248
|
+
_formatBubbleText (text) {
|
|
249
|
+
if (text === '') return text;
|
|
250
|
+
|
|
251
|
+
// Non-integers should be rounded to 2 decimal places (no more, no less), unless they're small enough that
|
|
252
|
+
// rounding would display them as 0.00. This matches 2.0's behavior:
|
|
253
|
+
// https://github.com/scratchfoundation/scratch-flash/blob/2e4a402ceb205a042887f54b26eebe1c2e6da6c0/src/scratch/ScratchSprite.as#L579-L585
|
|
254
|
+
if (typeof text === 'number' &&
|
|
255
|
+
Math.abs(text) >= 0.01 && text % 1 !== 0) {
|
|
256
|
+
text = text.toFixed(2);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Limit the length of the string.
|
|
260
|
+
text = String(text).substr(0, Scratch3LooksBlocks.SAY_BUBBLE_LIMIT);
|
|
261
|
+
|
|
262
|
+
return text;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* The entry point for say/think blocks. Clears existing bubble if the text is empty.
|
|
267
|
+
* Set the bubble custom state and then call _renderBubble.
|
|
268
|
+
* @param {!Target} target Target that say/think blocks are being called on.
|
|
269
|
+
* @param {!string} type Either "say" or "think"
|
|
270
|
+
* @param {!string} text The text for the bubble, empty string clears the bubble.
|
|
271
|
+
* @private
|
|
272
|
+
*/
|
|
273
|
+
_updateBubble (target, type, text) {
|
|
274
|
+
const bubbleState = this._getBubbleState(target);
|
|
275
|
+
bubbleState.type = type;
|
|
276
|
+
bubbleState.text = this._formatBubbleText(text);
|
|
277
|
+
bubbleState.usageId = uid();
|
|
278
|
+
this._renderBubble(target);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Retrieve the block primitives implemented by this package.
|
|
283
|
+
* @return {object.<string, Function>} Mapping of opcode to Function.
|
|
284
|
+
*/
|
|
285
|
+
getPrimitives () {
|
|
286
|
+
return {
|
|
287
|
+
looks_say: this.say,
|
|
288
|
+
looks_sayforsecs: this.sayforsecs,
|
|
289
|
+
looks_think: this.think,
|
|
290
|
+
looks_thinkforsecs: this.thinkforsecs,
|
|
291
|
+
looks_show: this.show,
|
|
292
|
+
looks_hide: this.hide,
|
|
293
|
+
looks_hideallsprites: () => {}, // legacy no-op block
|
|
294
|
+
looks_switchcostumeto: this.switchCostume,
|
|
295
|
+
looks_switchbackdropto: this.switchBackdrop,
|
|
296
|
+
looks_switchbackdroptoandwait: this.switchBackdropAndWait,
|
|
297
|
+
looks_nextcostume: this.nextCostume,
|
|
298
|
+
looks_nextbackdrop: this.nextBackdrop,
|
|
299
|
+
looks_changeeffectby: this.changeEffect,
|
|
300
|
+
looks_seteffectto: this.setEffect,
|
|
301
|
+
looks_cleargraphiceffects: this.clearEffects,
|
|
302
|
+
looks_changesizeby: this.changeSize,
|
|
303
|
+
looks_setsizeto: this.setSize,
|
|
304
|
+
looks_changestretchby: () => {}, // legacy no-op blocks
|
|
305
|
+
looks_setstretchto: () => {},
|
|
306
|
+
looks_gotofrontback: this.goToFrontBack,
|
|
307
|
+
looks_goforwardbackwardlayers: this.goForwardBackwardLayers,
|
|
308
|
+
looks_size: this.getSize,
|
|
309
|
+
looks_costumenumbername: this.getCostumeNumberName,
|
|
310
|
+
looks_backdropnumbername: this.getBackdropNumberName
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
getMonitored () {
|
|
315
|
+
return {
|
|
316
|
+
looks_size: {
|
|
317
|
+
isSpriteSpecific: true,
|
|
318
|
+
getId: targetId => `${targetId}_size`
|
|
319
|
+
},
|
|
320
|
+
looks_costumenumbername: {
|
|
321
|
+
isSpriteSpecific: true,
|
|
322
|
+
getId: (targetId, fields) => getMonitorIdForBlockWithArgs(`${targetId}_costumenumbername`, fields)
|
|
323
|
+
},
|
|
324
|
+
looks_backdropnumbername: {
|
|
325
|
+
getId: (_, fields) => getMonitorIdForBlockWithArgs('backdropnumbername', fields)
|
|
326
|
+
}
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
say (args, util) {
|
|
331
|
+
// @TODO in 2.0 calling say/think resets the right/left bias of the bubble
|
|
332
|
+
this.runtime.emit(Scratch3LooksBlocks.SAY_OR_THINK, util.target, 'say', args.MESSAGE);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
sayforsecs (args, util) {
|
|
336
|
+
this.say(args, util);
|
|
337
|
+
const target = util.target;
|
|
338
|
+
const usageId = this._getBubbleState(target).usageId;
|
|
339
|
+
return new Promise(resolve => {
|
|
340
|
+
this._bubbleTimeout = setTimeout(() => {
|
|
341
|
+
this._bubbleTimeout = null;
|
|
342
|
+
// Clear say bubble if it hasn't been changed and proceed.
|
|
343
|
+
if (this._getBubbleState(target).usageId === usageId) {
|
|
344
|
+
this._updateBubble(target, 'say', '');
|
|
345
|
+
}
|
|
346
|
+
resolve();
|
|
347
|
+
}, 1000 * args.SECS);
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
think (args, util) {
|
|
352
|
+
this.runtime.emit(Scratch3LooksBlocks.SAY_OR_THINK, util.target, 'think', args.MESSAGE);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
thinkforsecs (args, util) {
|
|
356
|
+
this.think(args, util);
|
|
357
|
+
const target = util.target;
|
|
358
|
+
const usageId = this._getBubbleState(target).usageId;
|
|
359
|
+
return new Promise(resolve => {
|
|
360
|
+
this._bubbleTimeout = setTimeout(() => {
|
|
361
|
+
this._bubbleTimeout = null;
|
|
362
|
+
// Clear think bubble if it hasn't been changed and proceed.
|
|
363
|
+
if (this._getBubbleState(target).usageId === usageId) {
|
|
364
|
+
this._updateBubble(target, 'think', '');
|
|
365
|
+
}
|
|
366
|
+
resolve();
|
|
367
|
+
}, 1000 * args.SECS);
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
show (args, util) {
|
|
372
|
+
util.target.setVisible(true);
|
|
373
|
+
this._renderBubble(util.target);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
hide (args, util) {
|
|
377
|
+
util.target.setVisible(false);
|
|
378
|
+
this._renderBubble(util.target);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* Utility function to set the costume of a target.
|
|
383
|
+
* Matches the behavior of Scratch 2.0 for different types of arguments.
|
|
384
|
+
* @param {!Target} target Target to set costume to.
|
|
385
|
+
* @param {Any} requestedCostume Costume requested, e.g., 0, 'name', etc.
|
|
386
|
+
* @param {boolean=} optZeroIndex Set to zero-index the requestedCostume.
|
|
387
|
+
* @return {Array.<!Thread>} Any threads started by this switch.
|
|
388
|
+
*/
|
|
389
|
+
_setCostume (target, requestedCostume, optZeroIndex) {
|
|
390
|
+
if (typeof requestedCostume === 'number') {
|
|
391
|
+
// Numbers should be treated as costume indices, always
|
|
392
|
+
target.setCostume(optZeroIndex ? requestedCostume : requestedCostume - 1);
|
|
393
|
+
} else {
|
|
394
|
+
// Strings should be treated as costume names, where possible
|
|
395
|
+
const costumeIndex = target.getCostumeIndexByName(requestedCostume.toString());
|
|
396
|
+
|
|
397
|
+
if (costumeIndex !== -1) {
|
|
398
|
+
target.setCostume(costumeIndex);
|
|
399
|
+
} else if (requestedCostume === 'next costume') {
|
|
400
|
+
target.setCostume(target.currentCostume + 1);
|
|
401
|
+
} else if (requestedCostume === 'previous costume') {
|
|
402
|
+
target.setCostume(target.currentCostume - 1);
|
|
403
|
+
// Try to cast the string to a number (and treat it as a costume index)
|
|
404
|
+
// Pure whitespace should not be treated as a number
|
|
405
|
+
// Note: isNaN will cast the string to a number before checking if it's NaN
|
|
406
|
+
} else if (!(isNaN(requestedCostume) || Cast.isWhiteSpace(requestedCostume))) {
|
|
407
|
+
target.setCostume(optZeroIndex ? Number(requestedCostume) : Number(requestedCostume) - 1);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
// Per 2.0, 'switch costume' can't start threads even in the Stage.
|
|
412
|
+
return [];
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
/**
|
|
416
|
+
* Utility function to set the backdrop of a target.
|
|
417
|
+
* Matches the behavior of Scratch 2.0 for different types of arguments.
|
|
418
|
+
* @param {!Target} stage Target to set backdrop to.
|
|
419
|
+
* @param {Any} requestedBackdrop Backdrop requested, e.g., 0, 'name', etc.
|
|
420
|
+
* @param {boolean=} optZeroIndex Set to zero-index the requestedBackdrop.
|
|
421
|
+
* @return {Array.<!Thread>} Any threads started by this switch.
|
|
422
|
+
*/
|
|
423
|
+
_setBackdrop (stage, requestedBackdrop, optZeroIndex) {
|
|
424
|
+
if (typeof requestedBackdrop === 'number') {
|
|
425
|
+
// Numbers should be treated as backdrop indices, always
|
|
426
|
+
stage.setCostume(optZeroIndex ? requestedBackdrop : requestedBackdrop - 1);
|
|
427
|
+
} else {
|
|
428
|
+
// Strings should be treated as backdrop names where possible
|
|
429
|
+
const costumeIndex = stage.getCostumeIndexByName(requestedBackdrop.toString());
|
|
430
|
+
|
|
431
|
+
if (costumeIndex !== -1) {
|
|
432
|
+
stage.setCostume(costumeIndex);
|
|
433
|
+
} else if (requestedBackdrop === 'next backdrop') {
|
|
434
|
+
stage.setCostume(stage.currentCostume + 1);
|
|
435
|
+
} else if (requestedBackdrop === 'previous backdrop') {
|
|
436
|
+
stage.setCostume(stage.currentCostume - 1);
|
|
437
|
+
} else if (requestedBackdrop === 'random backdrop') {
|
|
438
|
+
const numCostumes = stage.getCostumes().length;
|
|
439
|
+
if (numCostumes > 1) {
|
|
440
|
+
// Don't pick the current backdrop, so that the block
|
|
441
|
+
// will always have an observable effect.
|
|
442
|
+
const lowerBound = 0;
|
|
443
|
+
const upperBound = numCostumes - 1;
|
|
444
|
+
const costumeToExclude = stage.currentCostume;
|
|
445
|
+
|
|
446
|
+
const nextCostume = MathUtil.inclusiveRandIntWithout(lowerBound, upperBound, costumeToExclude);
|
|
447
|
+
|
|
448
|
+
stage.setCostume(nextCostume);
|
|
449
|
+
}
|
|
450
|
+
// Try to cast the string to a number (and treat it as a costume index)
|
|
451
|
+
// Pure whitespace should not be treated as a number
|
|
452
|
+
// Note: isNaN will cast the string to a number before checking if it's NaN
|
|
453
|
+
} else if (!(isNaN(requestedBackdrop) || Cast.isWhiteSpace(requestedBackdrop))) {
|
|
454
|
+
stage.setCostume(optZeroIndex ? Number(requestedBackdrop) : Number(requestedBackdrop) - 1);
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
const newName = stage.getCostumes()[stage.currentCostume].name;
|
|
459
|
+
return this.runtime.startHats('event_whenbackdropswitchesto', {
|
|
460
|
+
BACKDROP: newName
|
|
461
|
+
});
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
switchCostume (args, util) {
|
|
465
|
+
this._setCostume(util.target, args.COSTUME);
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
nextCostume (args, util) {
|
|
469
|
+
this._setCostume(
|
|
470
|
+
util.target, util.target.currentCostume + 1, true
|
|
471
|
+
);
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
switchBackdrop (args) {
|
|
475
|
+
this._setBackdrop(this.runtime.getTargetForStage(), args.BACKDROP);
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
switchBackdropAndWait (args, util) {
|
|
479
|
+
// Have we run before, starting threads?
|
|
480
|
+
if (!util.stackFrame.startedThreads) {
|
|
481
|
+
// No - switch the backdrop.
|
|
482
|
+
util.stackFrame.startedThreads = (
|
|
483
|
+
this._setBackdrop(
|
|
484
|
+
this.runtime.getTargetForStage(),
|
|
485
|
+
args.BACKDROP
|
|
486
|
+
)
|
|
487
|
+
);
|
|
488
|
+
if (util.stackFrame.startedThreads.length === 0) {
|
|
489
|
+
// Nothing was started.
|
|
490
|
+
return;
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
// We've run before; check if the wait is still going on.
|
|
494
|
+
const instance = this;
|
|
495
|
+
// Scratch 2 considers threads to be waiting if they are still in
|
|
496
|
+
// runtime.threads. Threads that have run all their blocks, or are
|
|
497
|
+
// marked done but still in runtime.threads are still considered to
|
|
498
|
+
// be waiting.
|
|
499
|
+
const waiting = util.stackFrame.startedThreads
|
|
500
|
+
.some(thread => instance.runtime.threads.indexOf(thread) !== -1);
|
|
501
|
+
if (waiting) {
|
|
502
|
+
// If all threads are waiting for the next tick or later yield
|
|
503
|
+
// for a tick as well. Otherwise yield until the next loop of
|
|
504
|
+
// the threads.
|
|
505
|
+
if (
|
|
506
|
+
util.stackFrame.startedThreads
|
|
507
|
+
.every(thread => instance.runtime.isWaitingThread(thread))
|
|
508
|
+
) {
|
|
509
|
+
util.yieldTick();
|
|
510
|
+
} else {
|
|
511
|
+
util.yield();
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
nextBackdrop () {
|
|
517
|
+
const stage = this.runtime.getTargetForStage();
|
|
518
|
+
this._setBackdrop(
|
|
519
|
+
stage, stage.currentCostume + 1, true
|
|
520
|
+
);
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
clampEffect (effect, value) {
|
|
524
|
+
let clampedValue = value;
|
|
525
|
+
switch (effect) {
|
|
526
|
+
case 'ghost':
|
|
527
|
+
clampedValue = MathUtil.clamp(value,
|
|
528
|
+
Scratch3LooksBlocks.EFFECT_GHOST_LIMIT.min,
|
|
529
|
+
Scratch3LooksBlocks.EFFECT_GHOST_LIMIT.max);
|
|
530
|
+
break;
|
|
531
|
+
case 'brightness':
|
|
532
|
+
clampedValue = MathUtil.clamp(value,
|
|
533
|
+
Scratch3LooksBlocks.EFFECT_BRIGHTNESS_LIMIT.min,
|
|
534
|
+
Scratch3LooksBlocks.EFFECT_BRIGHTNESS_LIMIT.max);
|
|
535
|
+
break;
|
|
536
|
+
}
|
|
537
|
+
return clampedValue;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
changeEffect (args, util) {
|
|
541
|
+
const effect = Cast.toString(args.EFFECT).toLowerCase();
|
|
542
|
+
const change = Cast.toNumber(args.CHANGE);
|
|
543
|
+
if (!Object.prototype.hasOwnProperty.call(util.target.effects, effect)) return;
|
|
544
|
+
let newValue = change + util.target.effects[effect];
|
|
545
|
+
newValue = this.clampEffect(effect, newValue);
|
|
546
|
+
util.target.setEffect(effect, newValue);
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
setEffect (args, util) {
|
|
550
|
+
const effect = Cast.toString(args.EFFECT).toLowerCase();
|
|
551
|
+
let value = Cast.toNumber(args.VALUE);
|
|
552
|
+
value = this.clampEffect(effect, value);
|
|
553
|
+
util.target.setEffect(effect, value);
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
clearEffects (args, util) {
|
|
557
|
+
util.target.clearEffects();
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
changeSize (args, util) {
|
|
561
|
+
const change = Cast.toNumber(args.CHANGE);
|
|
562
|
+
util.target.setSize(util.target.size + change);
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
setSize (args, util) {
|
|
566
|
+
const size = Cast.toNumber(args.SIZE);
|
|
567
|
+
util.target.setSize(size);
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
goToFrontBack (args, util) {
|
|
571
|
+
if (!util.target.isStage) {
|
|
572
|
+
if (args.FRONT_BACK === 'front') {
|
|
573
|
+
util.target.goToFront();
|
|
574
|
+
} else {
|
|
575
|
+
util.target.goToBack();
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
goForwardBackwardLayers (args, util) {
|
|
581
|
+
if (!util.target.isStage) {
|
|
582
|
+
if (args.FORWARD_BACKWARD === 'forward') {
|
|
583
|
+
util.target.goForwardLayers(Cast.toNumber(args.NUM));
|
|
584
|
+
} else {
|
|
585
|
+
util.target.goBackwardLayers(Cast.toNumber(args.NUM));
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
getSize (args, util) {
|
|
591
|
+
return Math.round(util.target.size);
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
getBackdropNumberName (args) {
|
|
595
|
+
const stage = this.runtime.getTargetForStage();
|
|
596
|
+
if (args.NUMBER_NAME === 'number') {
|
|
597
|
+
return stage.currentCostume + 1;
|
|
598
|
+
}
|
|
599
|
+
// Else return name
|
|
600
|
+
return stage.getCostumes()[stage.currentCostume].name;
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
getCostumeNumberName (args, util) {
|
|
604
|
+
if (args.NUMBER_NAME === 'number') {
|
|
605
|
+
return util.target.currentCostume + 1;
|
|
606
|
+
}
|
|
607
|
+
// Else return name
|
|
608
|
+
return util.target.getCostumes()[util.target.currentCostume].name;
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
module.exports = Scratch3LooksBlocks;
|