@scratch/scratch-vm 11.0.0-UEPR-176

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.
Files changed (178) hide show
  1. package/LICENSE +12 -0
  2. package/README.md +120 -0
  3. package/dist/node/extension-worker.js +2 -0
  4. package/dist/node/extension-worker.js.map +1 -0
  5. package/dist/node/scratch-vm.js +3 -0
  6. package/dist/node/scratch-vm.js.LICENSE.txt +205 -0
  7. package/dist/node/scratch-vm.js.map +1 -0
  8. package/dist/web/extension-worker.js +2 -0
  9. package/dist/web/extension-worker.js.map +1 -0
  10. package/dist/web/scratch-vm.js +3 -0
  11. package/dist/web/scratch-vm.js.LICENSE.txt +42 -0
  12. package/dist/web/scratch-vm.js.map +1 -0
  13. package/package.json +108 -0
  14. package/src/.eslintrc.js +7 -0
  15. package/src/blocks/scratch3_control.js +206 -0
  16. package/src/blocks/scratch3_core_example.js +69 -0
  17. package/src/blocks/scratch3_data.js +257 -0
  18. package/src/blocks/scratch3_event.js +137 -0
  19. package/src/blocks/scratch3_looks.js +612 -0
  20. package/src/blocks/scratch3_motion.js +288 -0
  21. package/src/blocks/scratch3_operators.js +154 -0
  22. package/src/blocks/scratch3_procedures.js +79 -0
  23. package/src/blocks/scratch3_sensing.js +336 -0
  24. package/src/blocks/scratch3_sound.js +350 -0
  25. package/src/dispatch/central-dispatch.js +143 -0
  26. package/src/dispatch/shared-dispatch.js +235 -0
  27. package/src/dispatch/worker-dispatch.js +110 -0
  28. package/src/engine/adapter.js +176 -0
  29. package/src/engine/block-utility.js +242 -0
  30. package/src/engine/blocks-execute-cache.js +19 -0
  31. package/src/engine/blocks-runtime-cache.js +78 -0
  32. package/src/engine/blocks.js +1299 -0
  33. package/src/engine/comment.js +56 -0
  34. package/src/engine/execute.js +580 -0
  35. package/src/engine/monitor-record.js +23 -0
  36. package/src/engine/mutation-adapter.js +48 -0
  37. package/src/engine/profiler.js +390 -0
  38. package/src/engine/runtime.js +2686 -0
  39. package/src/engine/scratch-blocks-constants.js +27 -0
  40. package/src/engine/sequencer.js +361 -0
  41. package/src/engine/stage-layering.js +29 -0
  42. package/src/engine/target.js +799 -0
  43. package/src/engine/thread.js +404 -0
  44. package/src/engine/variable.js +70 -0
  45. package/src/extension-support/argument-type.js +47 -0
  46. package/src/extension-support/block-type.js +50 -0
  47. package/src/extension-support/define-messages.js +18 -0
  48. package/src/extension-support/extension-manager.js +440 -0
  49. package/src/extension-support/extension-metadata.js +64 -0
  50. package/src/extension-support/extension-worker.js +59 -0
  51. package/src/extension-support/reporter-scope.js +18 -0
  52. package/src/extension-support/target-type.js +17 -0
  53. package/src/extensions/scratch3_boost/index.js +2113 -0
  54. package/src/extensions/scratch3_ev3/index.js +1355 -0
  55. package/src/extensions/scratch3_gdx_for/index.js +981 -0
  56. package/src/extensions/scratch3_gdx_for/scratch-link-device-adapter.js +44 -0
  57. package/src/extensions/scratch3_makeymakey/index.js +396 -0
  58. package/src/extensions/scratch3_microbit/index.js +984 -0
  59. package/src/extensions/scratch3_music/assets/drums/1-snare.mp3 +0 -0
  60. package/src/extensions/scratch3_music/assets/drums/10-wood-block.mp3 +0 -0
  61. package/src/extensions/scratch3_music/assets/drums/11-cowbell.mp3 +0 -0
  62. package/src/extensions/scratch3_music/assets/drums/12-triangle.mp3 +0 -0
  63. package/src/extensions/scratch3_music/assets/drums/13-bongo.mp3 +0 -0
  64. package/src/extensions/scratch3_music/assets/drums/14-conga.mp3 +0 -0
  65. package/src/extensions/scratch3_music/assets/drums/15-cabasa.mp3 +0 -0
  66. package/src/extensions/scratch3_music/assets/drums/16-guiro.mp3 +0 -0
  67. package/src/extensions/scratch3_music/assets/drums/17-vibraslap.mp3 +0 -0
  68. package/src/extensions/scratch3_music/assets/drums/18-cuica.mp3 +0 -0
  69. package/src/extensions/scratch3_music/assets/drums/2-bass-drum.mp3 +0 -0
  70. package/src/extensions/scratch3_music/assets/drums/3-side-stick.mp3 +0 -0
  71. package/src/extensions/scratch3_music/assets/drums/4-crash-cymbal.mp3 +0 -0
  72. package/src/extensions/scratch3_music/assets/drums/5-open-hi-hat.mp3 +0 -0
  73. package/src/extensions/scratch3_music/assets/drums/6-closed-hi-hat.mp3 +0 -0
  74. package/src/extensions/scratch3_music/assets/drums/7-tambourine.mp3 +0 -0
  75. package/src/extensions/scratch3_music/assets/drums/8-hand-clap.mp3 +0 -0
  76. package/src/extensions/scratch3_music/assets/drums/9-claves.mp3 +0 -0
  77. package/src/extensions/scratch3_music/assets/instruments/1-piano/108.mp3 +0 -0
  78. package/src/extensions/scratch3_music/assets/instruments/1-piano/24.mp3 +0 -0
  79. package/src/extensions/scratch3_music/assets/instruments/1-piano/36.mp3 +0 -0
  80. package/src/extensions/scratch3_music/assets/instruments/1-piano/48.mp3 +0 -0
  81. package/src/extensions/scratch3_music/assets/instruments/1-piano/60.mp3 +0 -0
  82. package/src/extensions/scratch3_music/assets/instruments/1-piano/72.mp3 +0 -0
  83. package/src/extensions/scratch3_music/assets/instruments/1-piano/84.mp3 +0 -0
  84. package/src/extensions/scratch3_music/assets/instruments/1-piano/96.mp3 +0 -0
  85. package/src/extensions/scratch3_music/assets/instruments/10-clarinet/48.mp3 +0 -0
  86. package/src/extensions/scratch3_music/assets/instruments/10-clarinet/60.mp3 +0 -0
  87. package/src/extensions/scratch3_music/assets/instruments/11-saxophone/36.mp3 +0 -0
  88. package/src/extensions/scratch3_music/assets/instruments/11-saxophone/60.mp3 +0 -0
  89. package/src/extensions/scratch3_music/assets/instruments/11-saxophone/84.mp3 +0 -0
  90. package/src/extensions/scratch3_music/assets/instruments/12-flute/60.mp3 +0 -0
  91. package/src/extensions/scratch3_music/assets/instruments/12-flute/72.mp3 +0 -0
  92. package/src/extensions/scratch3_music/assets/instruments/13-wooden-flute/60.mp3 +0 -0
  93. package/src/extensions/scratch3_music/assets/instruments/13-wooden-flute/72.mp3 +0 -0
  94. package/src/extensions/scratch3_music/assets/instruments/14-bassoon/36.mp3 +0 -0
  95. package/src/extensions/scratch3_music/assets/instruments/14-bassoon/48.mp3 +0 -0
  96. package/src/extensions/scratch3_music/assets/instruments/14-bassoon/60.mp3 +0 -0
  97. package/src/extensions/scratch3_music/assets/instruments/15-choir/48.mp3 +0 -0
  98. package/src/extensions/scratch3_music/assets/instruments/15-choir/60.mp3 +0 -0
  99. package/src/extensions/scratch3_music/assets/instruments/15-choir/72.mp3 +0 -0
  100. package/src/extensions/scratch3_music/assets/instruments/16-vibraphone/60.mp3 +0 -0
  101. package/src/extensions/scratch3_music/assets/instruments/16-vibraphone/72.mp3 +0 -0
  102. package/src/extensions/scratch3_music/assets/instruments/17-music-box/60.mp3 +0 -0
  103. package/src/extensions/scratch3_music/assets/instruments/18-steel-drum/60.mp3 +0 -0
  104. package/src/extensions/scratch3_music/assets/instruments/19-marimba/60.mp3 +0 -0
  105. package/src/extensions/scratch3_music/assets/instruments/2-electric-piano/60.mp3 +0 -0
  106. package/src/extensions/scratch3_music/assets/instruments/20-synth-lead/60.mp3 +0 -0
  107. package/src/extensions/scratch3_music/assets/instruments/21-synth-pad/60.mp3 +0 -0
  108. package/src/extensions/scratch3_music/assets/instruments/3-organ/60.mp3 +0 -0
  109. package/src/extensions/scratch3_music/assets/instruments/4-guitar/60.mp3 +0 -0
  110. package/src/extensions/scratch3_music/assets/instruments/5-electric-guitar/60.mp3 +0 -0
  111. package/src/extensions/scratch3_music/assets/instruments/6-bass/36.mp3 +0 -0
  112. package/src/extensions/scratch3_music/assets/instruments/6-bass/48.mp3 +0 -0
  113. package/src/extensions/scratch3_music/assets/instruments/7-pizzicato/60.mp3 +0 -0
  114. package/src/extensions/scratch3_music/assets/instruments/8-cello/36.mp3 +0 -0
  115. package/src/extensions/scratch3_music/assets/instruments/8-cello/48.mp3 +0 -0
  116. package/src/extensions/scratch3_music/assets/instruments/8-cello/60.mp3 +0 -0
  117. package/src/extensions/scratch3_music/assets/instruments/9-trombone/36.mp3 +0 -0
  118. package/src/extensions/scratch3_music/assets/instruments/9-trombone/48.mp3 +0 -0
  119. package/src/extensions/scratch3_music/assets/instruments/9-trombone/60.mp3 +0 -0
  120. package/src/extensions/scratch3_music/index.js +1333 -0
  121. package/src/extensions/scratch3_music/manifest.js +63 -0
  122. package/src/extensions/scratch3_pen/index.js +770 -0
  123. package/src/extensions/scratch3_speech2text/index.js +700 -0
  124. package/src/extensions/scratch3_text2speech/index.js +766 -0
  125. package/src/extensions/scratch3_translate/index.js +286 -0
  126. package/src/extensions/scratch3_video_sensing/debug.js +13 -0
  127. package/src/extensions/scratch3_video_sensing/index.js +594 -0
  128. package/src/extensions/scratch3_video_sensing/library.js +384 -0
  129. package/src/extensions/scratch3_video_sensing/math.js +76 -0
  130. package/src/extensions/scratch3_video_sensing/view.js +509 -0
  131. package/src/extensions/scratch3_wedo2/index.js +1616 -0
  132. package/src/import/load-costume.js +416 -0
  133. package/src/import/load-sound.js +116 -0
  134. package/src/index.js +10 -0
  135. package/src/io/ble.js +256 -0
  136. package/src/io/bt.js +202 -0
  137. package/src/io/clock.js +39 -0
  138. package/src/io/cloud.js +169 -0
  139. package/src/io/keyboard.js +146 -0
  140. package/src/io/mouse.js +145 -0
  141. package/src/io/mouseWheel.js +28 -0
  142. package/src/io/userData.js +24 -0
  143. package/src/io/video.js +210 -0
  144. package/src/playground/benchmark.css +83 -0
  145. package/src/playground/benchmark.js +753 -0
  146. package/src/playground/index.html +111 -0
  147. package/src/playground/suite.css +78 -0
  148. package/src/playground/suite.html +25 -0
  149. package/src/playground/suite.js +544 -0
  150. package/src/playground/video-sensing.html +18 -0
  151. package/src/playground/video-sensing.js +133 -0
  152. package/src/serialization/deserialize-assets.js +178 -0
  153. package/src/serialization/sb2.js +1295 -0
  154. package/src/serialization/sb2_specmap.js +1818 -0
  155. package/src/serialization/sb3.js +1320 -0
  156. package/src/serialization/serialize-assets.js +60 -0
  157. package/src/sprites/rendered-target.js +1115 -0
  158. package/src/sprites/sprite.js +177 -0
  159. package/src/util/base64-util.js +48 -0
  160. package/src/util/cast.js +218 -0
  161. package/src/util/clone.js +17 -0
  162. package/src/util/color.js +204 -0
  163. package/src/util/fetch-with-timeout.js +53 -0
  164. package/src/util/get-monitor-id.js +33 -0
  165. package/src/util/jsonrpc.js +114 -0
  166. package/src/util/log.js +4 -0
  167. package/src/util/math-util.js +122 -0
  168. package/src/util/maybe-format-message.js +18 -0
  169. package/src/util/new-block-ids.js +33 -0
  170. package/src/util/rateLimiter.js +73 -0
  171. package/src/util/scratch-link-websocket.js +133 -0
  172. package/src/util/string-util.js +94 -0
  173. package/src/util/task-queue.js +203 -0
  174. package/src/util/timer.js +103 -0
  175. package/src/util/uid.js +29 -0
  176. package/src/util/variable-util.js +48 -0
  177. package/src/util/xml-escape.js +33 -0
  178. package/src/virtual-machine.js +1586 -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;