mirror of
https://github.com/scratchfoundation/scratch-vm.git
synced 2024-12-23 14:32:59 -05:00
Merge remote-tracking branch 'refs/remotes/LLK/develop' into optimise/reduceGetBlockCalls
# Conflicts: # src/engine/execute.js
This commit is contained in:
commit
220d854f28
15 changed files with 414 additions and 45 deletions
20
package.json
20
package.json
|
@ -25,15 +25,15 @@
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"adm-zip": "0.4.7",
|
"adm-zip": "0.4.7",
|
||||||
"babel-eslint": "7.1.1",
|
"babel-eslint": "^7.1.1",
|
||||||
"copy-webpack-plugin": "4.0.1",
|
"copy-webpack-plugin": "4.0.1",
|
||||||
"eslint": "3.15.0",
|
"eslint": "^3.16.0",
|
||||||
"eslint-config-scratch": "^3.1.0",
|
"eslint-config-scratch": "^3.1.0",
|
||||||
"expose-loader": "0.7.1",
|
"expose-loader": "0.7.3",
|
||||||
"gh-pages": "0.12.0",
|
"gh-pages": "^0.12.0",
|
||||||
"highlightjs": "9.8.0",
|
"highlightjs": "^9.8.0",
|
||||||
"htmlparser2": "3.9.2",
|
"htmlparser2": "3.9.2",
|
||||||
"json": "9.0.4",
|
"json": "^9.0.4",
|
||||||
"lodash.defaultsdeep": "4.6.0",
|
"lodash.defaultsdeep": "4.6.0",
|
||||||
"minilog": "3.1.0",
|
"minilog": "3.1.0",
|
||||||
"promise": "7.1.1",
|
"promise": "7.1.1",
|
||||||
|
@ -41,10 +41,10 @@
|
||||||
"scratch-blocks": "latest",
|
"scratch-blocks": "latest",
|
||||||
"scratch-render": "latest",
|
"scratch-render": "latest",
|
||||||
"script-loader": "0.7.0",
|
"script-loader": "0.7.0",
|
||||||
"stats.js": "0.17.0",
|
"stats.js": "^0.17.0",
|
||||||
"tap": "10.1.2",
|
"tap": "^10.2.0",
|
||||||
"travis-after-all": "1.4.4",
|
"travis-after-all": "^1.4.4",
|
||||||
"webpack": "2.2.1",
|
"webpack": "2.2.1",
|
||||||
"webpack-dev-server": "2.3.0"
|
"webpack-dev-server": "^2.4.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,6 +33,36 @@ Scratch3SoundBlocks.DEFAULT_SOUND_STATE = {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The minimum and maximum MIDI note numbers, for clamping the input to play note.
|
||||||
|
* @type {{min: number, max: number}}
|
||||||
|
*/
|
||||||
|
Scratch3SoundBlocks.MIDI_NOTE_RANGE = {min: 36, max: 96}; // C2 to C7
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The minimum and maximum beat values, for clamping the duration of play note, play drum and rest.
|
||||||
|
* 100 beats at the default tempo of 60bpm is 100 seconds.
|
||||||
|
* @type {{min: number, max: number}}
|
||||||
|
*/
|
||||||
|
Scratch3SoundBlocks.BEAT_RANGE = {min: 0, max: 100};
|
||||||
|
|
||||||
|
/** The minimum and maximum tempo values, in bpm.
|
||||||
|
* @type {{min: number, max: number}}
|
||||||
|
*/
|
||||||
|
Scratch3SoundBlocks.TEMPO_RANGE = {min: 20, max: 500};
|
||||||
|
|
||||||
|
/** The minimum and maximum values for each sound effect.
|
||||||
|
* @type {{effect:{min: number, max: number}}}
|
||||||
|
*/
|
||||||
|
Scratch3SoundBlocks.EFFECT_RANGE = {
|
||||||
|
pitch: {min: -600, max: 600}, // -5 to 5 octaves
|
||||||
|
pan: {min: -100, max: 100}, // 100% left to 100% right
|
||||||
|
echo: {min: 0, max: 100}, // 0 to max (75%) feedback
|
||||||
|
reverb: {min: 0, max: 100}, // wet/dry: 0 to 100% wet
|
||||||
|
fuzz: {min: 0, max: 100}, // wed/dry: 0 to 100% wet
|
||||||
|
robot: {min: 0, max: 600} // 0 to 5 octaves
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {Target} target - collect sound state for this target.
|
* @param {Target} target - collect sound state for this target.
|
||||||
* @returns {SoundState} the mutable sound state associated with that target. This will be created if necessary.
|
* @returns {SoundState} the mutable sound state associated with that target. This will be created if necessary.
|
||||||
|
@ -132,7 +162,9 @@ Scratch3SoundBlocks.prototype.stopAllSounds = function (args, util) {
|
||||||
|
|
||||||
Scratch3SoundBlocks.prototype.playNoteForBeats = function (args, util) {
|
Scratch3SoundBlocks.prototype.playNoteForBeats = function (args, util) {
|
||||||
var note = Cast.toNumber(args.NOTE);
|
var note = Cast.toNumber(args.NOTE);
|
||||||
|
note = MathUtil.clamp(note, Scratch3SoundBlocks.MIDI_NOTE_RANGE.min, Scratch3SoundBlocks.MIDI_NOTE_RANGE.max);
|
||||||
var beats = Cast.toNumber(args.BEATS);
|
var beats = Cast.toNumber(args.BEATS);
|
||||||
|
beats = this._clampBeats(beats);
|
||||||
var soundState = this._getSoundState(util.target);
|
var soundState = this._getSoundState(util.target);
|
||||||
var inst = soundState.currentInstrument;
|
var inst = soundState.currentInstrument;
|
||||||
var vol = soundState.volume;
|
var vol = soundState.volume;
|
||||||
|
@ -146,16 +178,22 @@ Scratch3SoundBlocks.prototype.playDrumForBeats = function (args, util) {
|
||||||
if (typeof this.runtime.audioEngine === 'undefined') return;
|
if (typeof this.runtime.audioEngine === 'undefined') return;
|
||||||
drum = MathUtil.wrapClamp(drum, 0, this.runtime.audioEngine.numDrums);
|
drum = MathUtil.wrapClamp(drum, 0, this.runtime.audioEngine.numDrums);
|
||||||
var beats = Cast.toNumber(args.BEATS);
|
var beats = Cast.toNumber(args.BEATS);
|
||||||
|
beats = this._clampBeats(beats);
|
||||||
if (util.target.audioPlayer === null) return;
|
if (util.target.audioPlayer === null) return;
|
||||||
return util.target.audioPlayer.playDrumForBeats(drum, beats);
|
return util.target.audioPlayer.playDrumForBeats(drum, beats);
|
||||||
};
|
};
|
||||||
|
|
||||||
Scratch3SoundBlocks.prototype.restForBeats = function (args) {
|
Scratch3SoundBlocks.prototype.restForBeats = function (args) {
|
||||||
var beats = Cast.toNumber(args.BEATS);
|
var beats = Cast.toNumber(args.BEATS);
|
||||||
|
beats = this._clampBeats(beats);
|
||||||
if (typeof this.runtime.audioEngine === 'undefined') return;
|
if (typeof this.runtime.audioEngine === 'undefined') return;
|
||||||
return this.runtime.audioEngine.waitForBeats(beats);
|
return this.runtime.audioEngine.waitForBeats(beats);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Scratch3SoundBlocks.prototype._clampBeats = function (beats) {
|
||||||
|
return MathUtil.clamp(beats, Scratch3SoundBlocks.BEAT_RANGE.min, Scratch3SoundBlocks.BEAT_RANGE.max);
|
||||||
|
};
|
||||||
|
|
||||||
Scratch3SoundBlocks.prototype.setInstrument = function (args, util) {
|
Scratch3SoundBlocks.prototype.setInstrument = function (args, util) {
|
||||||
var soundState = this._getSoundState(util.target);
|
var soundState = this._getSoundState(util.target);
|
||||||
var instNum = Cast.toNumber(args.INSTRUMENT);
|
var instNum = Cast.toNumber(args.INSTRUMENT);
|
||||||
|
@ -167,25 +205,29 @@ Scratch3SoundBlocks.prototype.setInstrument = function (args, util) {
|
||||||
};
|
};
|
||||||
|
|
||||||
Scratch3SoundBlocks.prototype.setEffect = function (args, util) {
|
Scratch3SoundBlocks.prototype.setEffect = function (args, util) {
|
||||||
var effect = Cast.toString(args.EFFECT).toLowerCase();
|
this._updateEffect(args, util, false);
|
||||||
var value = Cast.toNumber(args.VALUE);
|
|
||||||
|
|
||||||
var soundState = this._getSoundState(util.target);
|
|
||||||
if (!soundState.effects.hasOwnProperty(effect)) return;
|
|
||||||
|
|
||||||
soundState.effects[effect] = value;
|
|
||||||
if (util.target.audioPlayer === null) return;
|
|
||||||
util.target.audioPlayer.setEffect(effect, soundState.effects[effect]);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Scratch3SoundBlocks.prototype.changeEffect = function (args, util) {
|
Scratch3SoundBlocks.prototype.changeEffect = function (args, util) {
|
||||||
|
this._updateEffect(args, util, true);
|
||||||
|
};
|
||||||
|
|
||||||
|
Scratch3SoundBlocks.prototype._updateEffect = function (args, util, change) {
|
||||||
var effect = Cast.toString(args.EFFECT).toLowerCase();
|
var effect = Cast.toString(args.EFFECT).toLowerCase();
|
||||||
var value = Cast.toNumber(args.VALUE);
|
var value = Cast.toNumber(args.VALUE);
|
||||||
|
|
||||||
var soundState = this._getSoundState(util.target);
|
var soundState = this._getSoundState(util.target);
|
||||||
if (!soundState.effects.hasOwnProperty(effect)) return;
|
if (!soundState.effects.hasOwnProperty(effect)) return;
|
||||||
|
|
||||||
|
if (change) {
|
||||||
soundState.effects[effect] += value;
|
soundState.effects[effect] += value;
|
||||||
|
} else {
|
||||||
|
soundState.effects[effect] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
var effectRange = Scratch3SoundBlocks.EFFECT_RANGE[effect];
|
||||||
|
soundState.effects[effect] = MathUtil.clamp(soundState.effects[effect], effectRange.min, effectRange.max);
|
||||||
|
|
||||||
if (util.target.audioPlayer === null) return;
|
if (util.target.audioPlayer === null) return;
|
||||||
util.target.audioPlayer.setEffect(effect, soundState.effects[effect]);
|
util.target.audioPlayer.setEffect(effect, soundState.effects[effect]);
|
||||||
};
|
};
|
||||||
|
@ -193,6 +235,7 @@ Scratch3SoundBlocks.prototype.changeEffect = function (args, util) {
|
||||||
Scratch3SoundBlocks.prototype.clearEffects = function (args, util) {
|
Scratch3SoundBlocks.prototype.clearEffects = function (args, util) {
|
||||||
var soundState = this._getSoundState(util.target);
|
var soundState = this._getSoundState(util.target);
|
||||||
for (var effect in soundState.effects) {
|
for (var effect in soundState.effects) {
|
||||||
|
if (!soundState.effects.hasOwnProperty(effect)) continue;
|
||||||
soundState.effects[effect] = 0;
|
soundState.effects[effect] = 0;
|
||||||
}
|
}
|
||||||
if (util.target.audioPlayer === null) return;
|
if (util.target.audioPlayer === null) return;
|
||||||
|
@ -224,15 +267,21 @@ Scratch3SoundBlocks.prototype.getVolume = function (args, util) {
|
||||||
};
|
};
|
||||||
|
|
||||||
Scratch3SoundBlocks.prototype.setTempo = function (args) {
|
Scratch3SoundBlocks.prototype.setTempo = function (args) {
|
||||||
var value = Cast.toNumber(args.TEMPO);
|
var tempo = Cast.toNumber(args.TEMPO);
|
||||||
if (typeof this.runtime.audioEngine === 'undefined') return;
|
this._updateTempo(tempo);
|
||||||
this.runtime.audioEngine.setTempo(value);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Scratch3SoundBlocks.prototype.changeTempo = function (args) {
|
Scratch3SoundBlocks.prototype.changeTempo = function (args) {
|
||||||
var value = Cast.toNumber(args.TEMPO);
|
var change = Cast.toNumber(args.TEMPO);
|
||||||
if (typeof this.runtime.audioEngine === 'undefined') return;
|
if (typeof this.runtime.audioEngine === 'undefined') return;
|
||||||
this.runtime.audioEngine.changeTempo(value);
|
var tempo = change + this.runtime.audioEngine.currentTempo;
|
||||||
|
this._updateTempo(tempo);
|
||||||
|
};
|
||||||
|
|
||||||
|
Scratch3SoundBlocks.prototype._updateTempo = function (tempo) {
|
||||||
|
tempo = MathUtil.clamp(tempo, Scratch3SoundBlocks.TEMPO_RANGE.min, Scratch3SoundBlocks.TEMPO_RANGE.max);
|
||||||
|
if (typeof this.runtime.audioEngine === 'undefined') return;
|
||||||
|
this.runtime.audioEngine.setTempo(tempo);
|
||||||
};
|
};
|
||||||
|
|
||||||
Scratch3SoundBlocks.prototype.getTempo = function () {
|
Scratch3SoundBlocks.prototype.getTempo = function () {
|
||||||
|
|
|
@ -24,6 +24,7 @@ var domToBlocks = function (blocksDOM) {
|
||||||
// Flatten blocks object into a list.
|
// Flatten blocks object into a list.
|
||||||
var blocksList = [];
|
var blocksList = [];
|
||||||
for (var b in blocks) {
|
for (var b in blocks) {
|
||||||
|
if (!blocks.hasOwnProperty(b)) continue;
|
||||||
blocksList.push(blocks[b]);
|
blocksList.push(blocks[b]);
|
||||||
}
|
}
|
||||||
return blocksList;
|
return blocksList;
|
||||||
|
|
|
@ -145,6 +145,7 @@ Blocks.prototype.getTopLevelScript = function (id) {
|
||||||
*/
|
*/
|
||||||
Blocks.prototype.getProcedureDefinition = function (name) {
|
Blocks.prototype.getProcedureDefinition = function (name) {
|
||||||
for (var id in this._blocks) {
|
for (var id in this._blocks) {
|
||||||
|
if (!this._blocks.hasOwnProperty(id)) continue;
|
||||||
var block = this._blocks[id];
|
var block = this._blocks[id];
|
||||||
if ((block.opcode === 'procedures_defnoreturn' ||
|
if ((block.opcode === 'procedures_defnoreturn' ||
|
||||||
block.opcode === 'procedures_defreturn') &&
|
block.opcode === 'procedures_defreturn') &&
|
||||||
|
@ -162,6 +163,7 @@ Blocks.prototype.getProcedureDefinition = function (name) {
|
||||||
*/
|
*/
|
||||||
Blocks.prototype.getProcedureParamNames = function (name) {
|
Blocks.prototype.getProcedureParamNames = function (name) {
|
||||||
for (var id in this._blocks) {
|
for (var id in this._blocks) {
|
||||||
|
if (!this._blocks.hasOwnProperty(id)) continue;
|
||||||
var block = this._blocks[id];
|
var block = this._blocks[id];
|
||||||
if ((block.opcode === 'procedures_defnoreturn' ||
|
if ((block.opcode === 'procedures_defnoreturn' ||
|
||||||
block.opcode === 'procedures_defreturn') &&
|
block.opcode === 'procedures_defreturn') &&
|
||||||
|
@ -411,6 +413,7 @@ Blocks.prototype.blockToXML = function (blockId) {
|
||||||
}
|
}
|
||||||
// Add any inputs on this block.
|
// Add any inputs on this block.
|
||||||
for (var input in block.inputs) {
|
for (var input in block.inputs) {
|
||||||
|
if (!block.inputs.hasOwnProperty(input)) continue;
|
||||||
var blockInput = block.inputs[input];
|
var blockInput = block.inputs[input];
|
||||||
// Only encode a value tag if the value input is occupied.
|
// Only encode a value tag if the value input is occupied.
|
||||||
if (blockInput.block || blockInput.shadow) {
|
if (blockInput.block || blockInput.shadow) {
|
||||||
|
@ -427,6 +430,7 @@ Blocks.prototype.blockToXML = function (blockId) {
|
||||||
}
|
}
|
||||||
// Add any fields on this block.
|
// Add any fields on this block.
|
||||||
for (var field in block.fields) {
|
for (var field in block.fields) {
|
||||||
|
if (!block.fields.hasOwnProperty(field)) continue;
|
||||||
var blockField = block.fields[field];
|
var blockField = block.fields[field];
|
||||||
var value = blockField.value;
|
var value = blockField.value;
|
||||||
if (typeof value === 'string') {
|
if (typeof value === 'string') {
|
||||||
|
|
|
@ -121,11 +121,13 @@ var execute = function (sequencer, thread) {
|
||||||
|
|
||||||
// Add all fields on this block to the argValues.
|
// Add all fields on this block to the argValues.
|
||||||
for (var fieldName in fields) {
|
for (var fieldName in fields) {
|
||||||
|
if (!fields.hasOwnProperty(fieldName)) continue;
|
||||||
argValues[fieldName] = fields[fieldName].value;
|
argValues[fieldName] = fields[fieldName].value;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Recursively evaluate input blocks.
|
// Recursively evaluate input blocks.
|
||||||
for (var inputName in inputs) {
|
for (var inputName in inputs) {
|
||||||
|
if (!inputs.hasOwnProperty(inputName)) continue;
|
||||||
var input = inputs[inputName];
|
var input = inputs[inputName];
|
||||||
var inputBlockId = input.block;
|
var inputBlockId = input.block;
|
||||||
// Is there no value for this input waiting in the stack frame?
|
// Is there no value for this input waiting in the stack frame?
|
||||||
|
@ -227,8 +229,23 @@ var execute = function (sequencer, thread) {
|
||||||
primitiveReportedValue.then(function (resolvedValue) {
|
primitiveReportedValue.then(function (resolvedValue) {
|
||||||
handleReport(resolvedValue);
|
handleReport(resolvedValue);
|
||||||
if (typeof resolvedValue === 'undefined') {
|
if (typeof resolvedValue === 'undefined') {
|
||||||
|
do {
|
||||||
|
// In the case that the promise is the last block in the current thread stack
|
||||||
|
// We need to pop out repeatedly until we find the next block.
|
||||||
var popped = thread.popStack();
|
var popped = thread.popStack();
|
||||||
|
if (popped === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
var nextBlockId = thread.target.blocks.getNextBlock(popped);
|
var nextBlockId = thread.target.blocks.getNextBlock(popped);
|
||||||
|
if (nextBlockId !== null) {
|
||||||
|
// A next block exists so break out this loop
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
// Investigate the next block and if not in a loop,
|
||||||
|
// then repeat and pop the next item off the stack frame
|
||||||
|
var stackFrame = thread.peekStackFrame();
|
||||||
|
} while (stackFrame !== null && !stackFrame.isLoop);
|
||||||
|
|
||||||
thread.pushStack(nextBlockId);
|
thread.pushStack(nextBlockId);
|
||||||
} else {
|
} else {
|
||||||
thread.popStack();
|
thread.popStack();
|
||||||
|
|
|
@ -441,6 +441,7 @@ Runtime.prototype.startHats = function (requestedHatOpcode,
|
||||||
var newThreads = [];
|
var newThreads = [];
|
||||||
|
|
||||||
for (var opts in optMatchFields) {
|
for (var opts in optMatchFields) {
|
||||||
|
if (!optMatchFields.hasOwnProperty(opts)) continue;
|
||||||
optMatchFields[opts] = optMatchFields[opts].toUpperCase();
|
optMatchFields[opts] = optMatchFields[opts].toUpperCase();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -465,6 +466,7 @@ Runtime.prototype.startHats = function (requestedHatOpcode,
|
||||||
if (Object.keys(hatFields).length === 0) {
|
if (Object.keys(hatFields).length === 0) {
|
||||||
var hatInputs = blocks.getInputs(block);
|
var hatInputs = blocks.getInputs(block);
|
||||||
for (var input in hatInputs) {
|
for (var input in hatInputs) {
|
||||||
|
if (!hatInputs.hasOwnProperty(input)) continue;
|
||||||
var id = hatInputs[input].block;
|
var id = hatInputs[input].block;
|
||||||
var inpBlock = blocks.getBlock(id);
|
var inpBlock = blocks.getBlock(id);
|
||||||
var fields = blocks.getFields(inpBlock);
|
var fields = blocks.getFields(inpBlock);
|
||||||
|
@ -595,6 +597,7 @@ Runtime.prototype.stopAll = function () {
|
||||||
Runtime.prototype._step = function () {
|
Runtime.prototype._step = function () {
|
||||||
// Find all edge-activated hats, and add them to threads to be evaluated.
|
// Find all edge-activated hats, and add them to threads to be evaluated.
|
||||||
for (var hatType in this._hats) {
|
for (var hatType in this._hats) {
|
||||||
|
if (!this._hats.hasOwnProperty(hatType)) continue;
|
||||||
var hat = this._hats[hatType];
|
var hat = this._hats[hatType];
|
||||||
if (hat.edgeActivated) {
|
if (hat.edgeActivated) {
|
||||||
this.startHats(hatType);
|
this.startHats(hatType);
|
||||||
|
@ -793,6 +796,18 @@ Runtime.prototype.getSpriteTargetByName = function (spriteName) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a target by its drawable id.
|
||||||
|
* @param {number} drawableID drawable id of target to find
|
||||||
|
* @return {?Target} The target, if found
|
||||||
|
*/
|
||||||
|
Runtime.prototype.getTargetByDrawableId = function (drawableID) {
|
||||||
|
for (var i = 0; i < this.targets.length; i++) {
|
||||||
|
var target = this.targets[i];
|
||||||
|
if (target.drawableID === drawableID) return target;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update the clone counter to track how many clones are created.
|
* Update the clone counter to track how many clones are created.
|
||||||
* @param {number} changeAmount How many clones have been created/destroyed.
|
* @param {number} changeAmount How many clones have been created/destroyed.
|
||||||
|
|
|
@ -104,6 +104,9 @@ var parseScratchObject = function (object, runtime, topLevel) {
|
||||||
if (object.hasOwnProperty('direction')) {
|
if (object.hasOwnProperty('direction')) {
|
||||||
target.direction = object.direction;
|
target.direction = object.direction;
|
||||||
}
|
}
|
||||||
|
if (object.hasOwnProperty('isDraggable')) {
|
||||||
|
target.draggable = object.isDraggable;
|
||||||
|
}
|
||||||
if (object.hasOwnProperty('scale')) {
|
if (object.hasOwnProperty('scale')) {
|
||||||
// SB2 stores as 1.0 = 100%; we use % in the VM.
|
// SB2 stores as 1.0 = 100%; we use % in the VM.
|
||||||
target.size = object.scale * 100;
|
target.size = object.scale * 100;
|
||||||
|
|
|
@ -46,7 +46,7 @@ Mouse.prototype.postData = function (data) {
|
||||||
}
|
}
|
||||||
if (typeof data.isDown !== 'undefined') {
|
if (typeof data.isDown !== 'undefined') {
|
||||||
this._isDown = data.isDown;
|
this._isDown = data.isDown;
|
||||||
if (this._isDown) {
|
if (!this._isDown) {
|
||||||
this._activateClickHats(data.x, data.y);
|
this._activateClickHats(data.x, data.y);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,6 +33,13 @@ var RenderedTarget = function (sprite, runtime) {
|
||||||
*/
|
*/
|
||||||
this.drawableID = null;
|
this.drawableID = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Drag state of this rendered target. If true, x/y position can't be
|
||||||
|
* changed by blocks.
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
this.dragging = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Map of current graphic effect values.
|
* Map of current graphic effect values.
|
||||||
* @type {!Object.<string, number>}
|
* @type {!Object.<string, number>}
|
||||||
|
@ -106,6 +113,12 @@ RenderedTarget.prototype.y = 0;
|
||||||
*/
|
*/
|
||||||
RenderedTarget.prototype.direction = 90;
|
RenderedTarget.prototype.direction = 90;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the rendered target is draggable on the stage
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
RenderedTarget.prototype.draggable = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether the rendered target is currently visible.
|
* Whether the rendered target is currently visible.
|
||||||
* @type {boolean}
|
* @type {boolean}
|
||||||
|
@ -160,22 +173,27 @@ RenderedTarget.prototype.rotationStyle = (
|
||||||
* Set the X and Y coordinates.
|
* Set the X and Y coordinates.
|
||||||
* @param {!number} x New X coordinate, in Scratch coordinates.
|
* @param {!number} x New X coordinate, in Scratch coordinates.
|
||||||
* @param {!number} y New Y coordinate, in Scratch coordinates.
|
* @param {!number} y New Y coordinate, in Scratch coordinates.
|
||||||
|
* @param {?boolean} force Force setting X/Y, in case of dragging
|
||||||
*/
|
*/
|
||||||
RenderedTarget.prototype.setXY = function (x, y) {
|
RenderedTarget.prototype.setXY = function (x, y, force) {
|
||||||
if (this.isStage) {
|
if (this.isStage) return;
|
||||||
return;
|
if (this.dragging && !force) return;
|
||||||
}
|
|
||||||
var oldX = this.x;
|
var oldX = this.x;
|
||||||
var oldY = this.y;
|
var oldY = this.y;
|
||||||
this.x = x;
|
|
||||||
this.y = y;
|
|
||||||
if (this.renderer) {
|
if (this.renderer) {
|
||||||
|
var position = this.renderer.getFencedPositionOfDrawable(this.drawableID, [x, y]);
|
||||||
|
this.x = position[0];
|
||||||
|
this.y = position[1];
|
||||||
|
|
||||||
this.renderer.updateDrawableProperties(this.drawableID, {
|
this.renderer.updateDrawableProperties(this.drawableID, {
|
||||||
position: [this.x, this.y]
|
position: position
|
||||||
});
|
});
|
||||||
if (this.visible) {
|
if (this.visible) {
|
||||||
this.runtime.requestRedraw();
|
this.runtime.requestRedraw();
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
this.x = x;
|
||||||
|
this.y = y;
|
||||||
}
|
}
|
||||||
this.emit(RenderedTarget.EVENT_TARGET_MOVED, this, oldX, oldY);
|
this.emit(RenderedTarget.EVENT_TARGET_MOVED, this, oldX, oldY);
|
||||||
this.runtime.spriteInfoReport(this);
|
this.runtime.spriteInfoReport(this);
|
||||||
|
@ -224,6 +242,16 @@ RenderedTarget.prototype.setDirection = function (direction) {
|
||||||
this.runtime.spriteInfoReport(this);
|
this.runtime.spriteInfoReport(this);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set draggability; i.e., whether it's able to be dragged in the player
|
||||||
|
* @param {!boolean} draggable True if should be draggable.
|
||||||
|
*/
|
||||||
|
RenderedTarget.prototype.setDraggable = function (draggable) {
|
||||||
|
if (this.isStage) return;
|
||||||
|
this.draggable = !!draggable;
|
||||||
|
this.runtime.spriteInfoReport(this);
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set a say bubble.
|
* Set a say bubble.
|
||||||
* @param {?string} type Type of say bubble: "say", "think", or null.
|
* @param {?string} type Type of say bubble: "say", "think", or null.
|
||||||
|
@ -315,6 +343,7 @@ RenderedTarget.prototype.setEffect = function (effectName, value) {
|
||||||
*/
|
*/
|
||||||
RenderedTarget.prototype.clearEffects = function () {
|
RenderedTarget.prototype.clearEffects = function () {
|
||||||
for (var effectName in this.effects) {
|
for (var effectName in this.effects) {
|
||||||
|
if (!this.effects.hasOwnProperty(effectName)) continue;
|
||||||
this.effects[effectName] = 0;
|
this.effects[effectName] = 0;
|
||||||
}
|
}
|
||||||
if (this.renderer) {
|
if (this.renderer) {
|
||||||
|
@ -422,20 +451,23 @@ RenderedTarget.prototype.updateAllDrawableProperties = function () {
|
||||||
if (this.renderer) {
|
if (this.renderer) {
|
||||||
var renderedDirectionScale = this._getRenderedDirectionAndScale();
|
var renderedDirectionScale = this._getRenderedDirectionAndScale();
|
||||||
var costume = this.sprite.costumes[this.currentCostume];
|
var costume = this.sprite.costumes[this.currentCostume];
|
||||||
|
var bitmapResolution = costume.bitmapResolution || 1;
|
||||||
var props = {
|
var props = {
|
||||||
position: [this.x, this.y],
|
position: [this.x, this.y],
|
||||||
direction: renderedDirectionScale.direction,
|
direction: renderedDirectionScale.direction,
|
||||||
|
draggable: this.draggable,
|
||||||
scale: renderedDirectionScale.scale,
|
scale: renderedDirectionScale.scale,
|
||||||
visible: this.visible,
|
visible: this.visible,
|
||||||
skin: costume.skin,
|
skin: costume.skin,
|
||||||
costumeResolution: costume.bitmapResolution,
|
costumeResolution: bitmapResolution,
|
||||||
rotationCenter: [
|
rotationCenter: [
|
||||||
costume.rotationCenterX / costume.bitmapResolution,
|
costume.rotationCenterX / bitmapResolution,
|
||||||
costume.rotationCenterY / costume.bitmapResolution
|
costume.rotationCenterY / bitmapResolution
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
for (var effectID in this.effects) {
|
for (var effectName in this.effects) {
|
||||||
props[effectID] = this.effects[effectID];
|
if (!this.effects.hasOwnProperty(effectName)) continue;
|
||||||
|
props[effectName] = this.effects[effectName];
|
||||||
}
|
}
|
||||||
this.renderer.updateDrawableProperties(this.drawableID, props);
|
this.renderer.updateDrawableProperties(this.drawableID, props);
|
||||||
if (this.visible) {
|
if (this.visible) {
|
||||||
|
@ -649,6 +681,7 @@ RenderedTarget.prototype.makeClone = function () {
|
||||||
newClone.x = this.x;
|
newClone.x = this.x;
|
||||||
newClone.y = this.y;
|
newClone.y = this.y;
|
||||||
newClone.direction = this.direction;
|
newClone.direction = this.direction;
|
||||||
|
newClone.draggable = this.draggable;
|
||||||
newClone.visible = this.visible;
|
newClone.visible = this.visible;
|
||||||
newClone.size = this.size;
|
newClone.size = this.size;
|
||||||
newClone.currentCostume = this.currentCostume;
|
newClone.currentCostume = this.currentCostume;
|
||||||
|
@ -673,9 +706,10 @@ RenderedTarget.prototype.onGreenFlag = function () {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when the project receives a "stop all"
|
* Called when the project receives a "stop all"
|
||||||
* Stop all sounds
|
* Stop all sounds and clear graphic effects.
|
||||||
*/
|
*/
|
||||||
RenderedTarget.prototype.onStopAll = function () {
|
RenderedTarget.prototype.onStopAll = function () {
|
||||||
|
this.clearEffects();
|
||||||
if (this.audioPlayer) {
|
if (this.audioPlayer) {
|
||||||
this.audioPlayer.stopAllSounds();
|
this.audioPlayer.stopAllSounds();
|
||||||
this.audioPlayer.clearEffects();
|
this.audioPlayer.clearEffects();
|
||||||
|
@ -687,15 +721,19 @@ RenderedTarget.prototype.onStopAll = function () {
|
||||||
* @param {object} data An object with sprite info data to set.
|
* @param {object} data An object with sprite info data to set.
|
||||||
*/
|
*/
|
||||||
RenderedTarget.prototype.postSpriteInfo = function (data) {
|
RenderedTarget.prototype.postSpriteInfo = function (data) {
|
||||||
|
var force = data.hasOwnProperty('force') ? data.force : null;
|
||||||
if (data.hasOwnProperty('x')) {
|
if (data.hasOwnProperty('x')) {
|
||||||
this.setXY(data.x, this.y);
|
this.setXY(data.x, this.y, force);
|
||||||
}
|
}
|
||||||
if (data.hasOwnProperty('y')) {
|
if (data.hasOwnProperty('y')) {
|
||||||
this.setXY(this.x, data.y);
|
this.setXY(this.x, data.y, force);
|
||||||
}
|
}
|
||||||
if (data.hasOwnProperty('direction')) {
|
if (data.hasOwnProperty('direction')) {
|
||||||
this.setDirection(data.direction);
|
this.setDirection(data.direction);
|
||||||
}
|
}
|
||||||
|
if (data.hasOwnProperty('draggable')) {
|
||||||
|
this.setDraggable(data.draggable);
|
||||||
|
}
|
||||||
if (data.hasOwnProperty('rotationStyle')) {
|
if (data.hasOwnProperty('rotationStyle')) {
|
||||||
this.setRotationStyle(data.rotationStyle);
|
this.setRotationStyle(data.rotationStyle);
|
||||||
}
|
}
|
||||||
|
@ -704,6 +742,20 @@ RenderedTarget.prototype.postSpriteInfo = function (data) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Put the sprite into the drag state. While in effect, setXY must be forced
|
||||||
|
*/
|
||||||
|
RenderedTarget.prototype.startDrag = function () {
|
||||||
|
this.dragging = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove the sprite from the drag state.
|
||||||
|
*/
|
||||||
|
RenderedTarget.prototype.stopDrag = function () {
|
||||||
|
this.dragging = false;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Serialize sprite info, used when emitting events about the sprite
|
* Serialize sprite info, used when emitting events about the sprite
|
||||||
* @returns {object} sprite data as a simple object
|
* @returns {object} sprite data as a simple object
|
||||||
|
@ -716,6 +768,7 @@ RenderedTarget.prototype.toJSON = function () {
|
||||||
x: this.x,
|
x: this.x,
|
||||||
y: this.y,
|
y: this.y,
|
||||||
direction: this.direction,
|
direction: this.direction,
|
||||||
|
draggable: this.draggable,
|
||||||
costume: this.getCurrentCostume(),
|
costume: this.getCurrentCostume(),
|
||||||
costumeCount: this.getCostumes().length,
|
costumeCount: this.getCostumes().length,
|
||||||
visible: this.visible,
|
visible: this.visible,
|
||||||
|
|
17
src/util/string-util.js
Normal file
17
src/util/string-util.js
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
var StringUtil = function () {};
|
||||||
|
|
||||||
|
StringUtil.withoutTrailingDigits = function (s) {
|
||||||
|
var i = s.length - 1;
|
||||||
|
while ((i >= 0) && ('0123456789'.indexOf(s.charAt(i)) > -1)) i--;
|
||||||
|
return s.slice(0, i + 1);
|
||||||
|
};
|
||||||
|
|
||||||
|
StringUtil.unusedName = function (name, existingNames) {
|
||||||
|
if (existingNames.indexOf(name) < 0) return name;
|
||||||
|
name = StringUtil.withoutTrailingDigits(name);
|
||||||
|
var i = 2;
|
||||||
|
while (existingNames.indexOf(name + i) >= 0) i++;
|
||||||
|
return name + i;
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = StringUtil;
|
|
@ -3,6 +3,9 @@ var util = require('util');
|
||||||
|
|
||||||
var Runtime = require('./engine/runtime');
|
var Runtime = require('./engine/runtime');
|
||||||
var sb2import = require('./import/sb2import');
|
var sb2import = require('./import/sb2import');
|
||||||
|
var StringUtil = require('./util/string-util');
|
||||||
|
|
||||||
|
var RESERVED_NAMES = ['_mouse_', '_stage_', '_edge_', '_myself_', '_random_'];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles connections between blocks, stage, and extensions.
|
* Handles connections between blocks, stage, and extensions.
|
||||||
|
@ -204,7 +207,15 @@ VirtualMachine.prototype.renameSprite = function (targetId, newName) {
|
||||||
if (!sprite) {
|
if (!sprite) {
|
||||||
throw new Error('No sprite associated with this target.');
|
throw new Error('No sprite associated with this target.');
|
||||||
}
|
}
|
||||||
sprite.name = newName;
|
if (newName && RESERVED_NAMES.indexOf(newName) === -1) {
|
||||||
|
var names = this.runtime.targets.filter(function (runtimeTarget) {
|
||||||
|
return runtimeTarget.isSprite();
|
||||||
|
}).map(function (runtimeTarget) {
|
||||||
|
return runtimeTarget.sprite.name;
|
||||||
|
});
|
||||||
|
|
||||||
|
sprite.name = StringUtil.unusedName(newName, names);
|
||||||
|
}
|
||||||
this.emitTargetsUpdate();
|
this.emitTargetsUpdate();
|
||||||
} else {
|
} else {
|
||||||
throw new Error('No target with the provided id.');
|
throw new Error('No target with the provided id.');
|
||||||
|
@ -328,6 +339,41 @@ VirtualMachine.prototype.emitWorkspaceUpdate = function () {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a target id for a drawable id. Useful for interacting with the renderer
|
||||||
|
* @param {int} drawableId The drawable id to request the target id for
|
||||||
|
* @returns {?string} The target id, if found. Will also be null if the target found is the stage.
|
||||||
|
*/
|
||||||
|
VirtualMachine.prototype.getTargetIdForDrawableId = function (drawableId) {
|
||||||
|
var target = this.runtime.getTargetByDrawableId(drawableId);
|
||||||
|
if (target && target.hasOwnProperty('id') && target.hasOwnProperty('isStage') && !target.isStage) {
|
||||||
|
return target.id;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Put a target into a "drag" state, during which its X/Y positions will be unaffected
|
||||||
|
* by blocks.
|
||||||
|
* @param {string} targetId The id for the target to put into a drag state
|
||||||
|
*/
|
||||||
|
VirtualMachine.prototype.startDrag = function (targetId) {
|
||||||
|
var target = this.runtime.getTargetById(targetId);
|
||||||
|
if (target) {
|
||||||
|
target.startDrag();
|
||||||
|
this.setEditingTarget(target.id);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove a target from a drag state, so blocks may begin affecting X/Y position again
|
||||||
|
* @param {string} targetId The id for the target to remove from the drag state
|
||||||
|
*/
|
||||||
|
VirtualMachine.prototype.stopDrag = function (targetId) {
|
||||||
|
var target = this.runtime.getTargetById(targetId);
|
||||||
|
if (target) target.stopDrag();
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Post/edit sprite info for the current editing target.
|
* Post/edit sprite info for the current editing target.
|
||||||
* @param {object} data An object with sprite info data to set.
|
* @param {object} data An object with sprite info data to set.
|
||||||
|
|
|
@ -26,7 +26,7 @@ test('complex', function (t) {
|
||||||
var targets = data.targetList;
|
var targets = data.targetList;
|
||||||
for (var i in targets) {
|
for (var i in targets) {
|
||||||
if (targets[i].isStage === true) continue;
|
if (targets[i].isStage === true) continue;
|
||||||
if (targets[i].name === 'test') continue;
|
if (targets[i].name.match(/test/)) continue;
|
||||||
|
|
||||||
vm.setEditingTarget(targets[i].id);
|
vm.setEditingTarget(targets[i].id);
|
||||||
vm.renameSprite(targets[i].id, 'test');
|
vm.renameSprite(targets[i].id, 'test');
|
||||||
|
@ -34,6 +34,7 @@ test('complex', function (t) {
|
||||||
x: 0,
|
x: 0,
|
||||||
y: 10,
|
y: 10,
|
||||||
direction: 90,
|
direction: 90,
|
||||||
|
draggable: true,
|
||||||
rotationStyle: 'all around',
|
rotationStyle: 'all around',
|
||||||
visible: true
|
visible: true
|
||||||
});
|
});
|
||||||
|
|
|
@ -11,3 +11,13 @@ test('clone effects', function (t) {
|
||||||
t.ok(a.effects !== b.effects);
|
t.ok(a.effects !== b.effects);
|
||||||
t.end();
|
t.end();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('#stopAll clears graphics effects', function (t) {
|
||||||
|
var spr = new Sprite();
|
||||||
|
var a = new RenderedTarget(spr, null);
|
||||||
|
var effectName = 'brightness';
|
||||||
|
a.setEffect(effectName, 100);
|
||||||
|
a.onStopAll();
|
||||||
|
t.equals(a.effects[effectName], 0);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
57
test/unit/util_string.js
Normal file
57
test/unit/util_string.js
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
var test = require('tap').test;
|
||||||
|
var StringUtil = require('../../src/util/string-util');
|
||||||
|
|
||||||
|
test('withoutTrailingDigits', function (t) {
|
||||||
|
t.strictEqual(StringUtil.withoutTrailingDigits('boeing747'), 'boeing');
|
||||||
|
t.strictEqual(StringUtil.withoutTrailingDigits('boeing747 '), 'boeing747 ');
|
||||||
|
t.strictEqual(StringUtil.withoutTrailingDigits('boeing𝟨'), 'boeing𝟨');
|
||||||
|
t.strictEqual(StringUtil.withoutTrailingDigits('boeing 747'), 'boeing ');
|
||||||
|
t.strictEqual(StringUtil.withoutTrailingDigits('747'), '');
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('unusedName', function (t) {
|
||||||
|
t.strictEqual(
|
||||||
|
StringUtil.unusedName(
|
||||||
|
'name',
|
||||||
|
['not the same name']
|
||||||
|
),
|
||||||
|
'name'
|
||||||
|
);
|
||||||
|
t.strictEqual(
|
||||||
|
StringUtil.unusedName(
|
||||||
|
'name',
|
||||||
|
['name']
|
||||||
|
),
|
||||||
|
'name2'
|
||||||
|
);
|
||||||
|
t.strictEqual(
|
||||||
|
StringUtil.unusedName(
|
||||||
|
'name',
|
||||||
|
['name30']
|
||||||
|
),
|
||||||
|
'name'
|
||||||
|
);
|
||||||
|
t.strictEqual(
|
||||||
|
StringUtil.unusedName(
|
||||||
|
'name',
|
||||||
|
['name', 'name2']
|
||||||
|
),
|
||||||
|
'name3'
|
||||||
|
);
|
||||||
|
t.strictEqual(
|
||||||
|
StringUtil.unusedName(
|
||||||
|
'name',
|
||||||
|
['name', 'name3']
|
||||||
|
),
|
||||||
|
'name2'
|
||||||
|
);
|
||||||
|
t.strictEqual(
|
||||||
|
StringUtil.unusedName(
|
||||||
|
'boeing747',
|
||||||
|
['boeing747']
|
||||||
|
),
|
||||||
|
'boeing2' // Yup, this matches scratch-flash...
|
||||||
|
);
|
||||||
|
t.end();
|
||||||
|
});
|
96
test/unit/virtual-machine.js
Normal file
96
test/unit/virtual-machine.js
Normal file
|
@ -0,0 +1,96 @@
|
||||||
|
var test = require('tap').test;
|
||||||
|
var VirtualMachine = require('../../src/virtual-machine.js');
|
||||||
|
|
||||||
|
test('renameSprite throws when there is no sprite with that id', function (t) {
|
||||||
|
var vm = new VirtualMachine();
|
||||||
|
vm.runtime.getTargetById = () => null;
|
||||||
|
t.throws(
|
||||||
|
(() => vm.renameSprite('id', 'name')),
|
||||||
|
new Error('No target with the provided id.')
|
||||||
|
);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('renameSprite throws when used on a non-sprite target', function (t) {
|
||||||
|
var vm = new VirtualMachine();
|
||||||
|
var fakeTarget = {
|
||||||
|
isSprite: () => false
|
||||||
|
};
|
||||||
|
vm.runtime.getTargetById = () => (fakeTarget);
|
||||||
|
t.throws(
|
||||||
|
(() => vm.renameSprite('id', 'name')),
|
||||||
|
new Error('Cannot rename non-sprite targets.')
|
||||||
|
);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('renameSprite throws when there is no sprite for given target', function (t) {
|
||||||
|
var vm = new VirtualMachine();
|
||||||
|
var fakeTarget = {
|
||||||
|
sprite: null,
|
||||||
|
isSprite: () => true
|
||||||
|
};
|
||||||
|
vm.runtime.getTargetById = () => (fakeTarget);
|
||||||
|
t.throws(
|
||||||
|
(() => vm.renameSprite('id', 'name')),
|
||||||
|
new Error('No sprite associated with this target.')
|
||||||
|
);
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('renameSprite sets the sprite name', function (t) {
|
||||||
|
var vm = new VirtualMachine();
|
||||||
|
var fakeTarget = {
|
||||||
|
sprite: {name: 'original'},
|
||||||
|
isSprite: () => true
|
||||||
|
};
|
||||||
|
vm.runtime.getTargetById = () => (fakeTarget);
|
||||||
|
vm.renameSprite('id', 'not-original');
|
||||||
|
t.equal(fakeTarget.sprite.name, 'not-original');
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('renameSprite does not set sprite names to an empty string', function (t) {
|
||||||
|
var vm = new VirtualMachine();
|
||||||
|
var fakeTarget = {
|
||||||
|
sprite: {name: 'original'},
|
||||||
|
isSprite: () => true
|
||||||
|
};
|
||||||
|
vm.runtime.getTargetById = () => (fakeTarget);
|
||||||
|
vm.renameSprite('id', '');
|
||||||
|
t.equal(fakeTarget.sprite.name, 'original');
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('renameSprite does not set sprite names to reserved names', function (t) {
|
||||||
|
var vm = new VirtualMachine();
|
||||||
|
var fakeTarget = {
|
||||||
|
sprite: {name: 'original'},
|
||||||
|
isSprite: () => true
|
||||||
|
};
|
||||||
|
vm.runtime.getTargetById = () => (fakeTarget);
|
||||||
|
vm.renameSprite('id', '_mouse_');
|
||||||
|
t.equal(fakeTarget.sprite.name, 'original');
|
||||||
|
t.end();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('renameSprite increments from existing sprite names', function (t) {
|
||||||
|
var vm = new VirtualMachine();
|
||||||
|
vm.emitTargetsUpdate = () => {};
|
||||||
|
vm.runtime.targets = [{
|
||||||
|
id: 'id1',
|
||||||
|
isSprite: () => true,
|
||||||
|
sprite: {
|
||||||
|
name: 'this name'
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
id: 'id2',
|
||||||
|
isSprite: () => true,
|
||||||
|
sprite: {
|
||||||
|
name: 'that name'
|
||||||
|
}
|
||||||
|
}];
|
||||||
|
vm.renameSprite('id1', 'that name');
|
||||||
|
t.equal(vm.runtime.targets[0].sprite.name, 'that name2');
|
||||||
|
t.end();
|
||||||
|
});
|
Loading…
Reference in a new issue