diff options
Diffstat (limited to 'objects.js')
| -rw-r--r-- | objects.js | 414 |
1 files changed, 399 insertions, 15 deletions
@@ -415,6 +415,11 @@ SpriteMorph.prototype.initBlocks = function () { spec: 'go back %n layers', defaults: [1] }, + doNotify: { + type: 'command', + category: 'looks', + spec: 'notification with title %s and content %s' + }, doScreenshot: { type: 'command', category: 'looks', @@ -459,6 +464,23 @@ SpriteMorph.prototype.initBlocks = function () { category: 'sound', spec: 'stop all sounds' }, + doSetVolume: { + type: 'command', + category: 'sound', + spec: 'set volume to %n %', + defaults: [100] + }, + doChangeVolume: { + type: 'command', + category: 'sound', + spec: 'change volume by %n', + defaults: [-10] + }, + reportVolume: { + type: 'reporter', + category: 'sound', + spec: 'volume' + }, doRest: { type: 'command', category: 'sound', @@ -569,6 +591,16 @@ SpriteMorph.prototype.initBlocks = function () { category: 'pen', spec: 'stamp' }, + doStreamCamera: { + type: 'command', + category: 'pen', + spec: 'start streaming from the camera' + }, + doStopCamera: { + type: 'command', + category: 'pen', + spec: 'stop streaming from the camera' + }, // Control receiveGo: { @@ -786,6 +818,23 @@ SpriteMorph.prototype.initBlocks = function () { category: 'sensing', spec: 'color %clr is touching %clr ?' }, + reportCameraMotion: { + only: SpriteMorph, + type: 'predicate', + category: 'sensing', + spec: 'camera motion at my position?' + }, + reportCameraDirection: { + only: SpriteMorph, + type: 'reporter', + category: 'sensing', + spec: 'camera motion direction' + }, + reportStreamingCamera: { + type: 'predicate', + category: 'sensing', + spec: 'streaming from the camera?' + }, colorFiltered: { dev: true, type: 'reporter', @@ -847,6 +896,11 @@ SpriteMorph.prototype.initBlocks = function () { category: 'sensing', spec: 'key %key pressed?' }, + getKeysPressed: { + type: 'reporter', + category: 'sensing', + spec: 'keys pressed' + }, reportDistanceTo: { type: 'reporter', category: 'sensing', @@ -895,6 +949,41 @@ SpriteMorph.prototype.initBlocks = function () { category: 'sensing', spec: 'current %dates' }, + doVibrate: { + type: 'command', + category: 'sensing', + spec: 'vibrate %n seconds' + }, + reportCompassHeading: { + type: 'reporter', + category: 'sensing', + spec: 'current compass heading' + }, + reportAccelerationX: { + type: 'reporter', + category: 'sensing', + spec: 'current acceleration along the x axes' + }, + reportAccelerationY: { + type: 'reporter', + category: 'sensing', + spec: 'current acceleration along the y axes' + }, + reportAccelerationZ: { + type: 'reporter', + category: 'sensing', + spec: 'current acceleration along the z axes' + }, + reportLanguage: { + type: 'reporter', + category: 'sensing', + spec: 'language' + }, + reportLocation: { + type: 'reporter', + category: 'sensing', + spec: 'location %locations' + }, // Operators reifyScript: { @@ -1176,6 +1265,29 @@ SpriteMorph.prototype.initBlocks = function () { defaults: [localize('each item')] }, + // peer to peer communication + receivePeerMessage: { + type: 'hat', + category: 'other', + spec: 'when I receive %upvar from %upvar', + defaults: [localize('message'), localize('peer')] + }, + sendPeerMessage: { + type: 'command', + category: 'other', + spec: 'send %s to %s' + }, + reportPeerId: { + type: 'reporter', + category: 'other', + spec: 'my peer id' + }, + reportPeerList: { + type: 'reporter', + category: 'other', + spec: 'peers online' + }, + // Code mapping - experimental doMapCodeOrHeader: { // experimental type: 'command', @@ -1322,6 +1434,8 @@ SpriteMorph.prototype.init = function (globals) { this.version = Date.now(); // for observer optimization this.isClone = false; // indicate a "temporary" Scratch-style clone this.cloneOriginName = ''; + this.volume = 100; + this.activeSounds = []; // sprite nesting properties this.parts = []; // not serialized, only anchor (name) @@ -1425,8 +1539,11 @@ SpriteMorph.prototype.appearIn = function (ide) { // SpriteMorph versioning SpriteMorph.prototype.setName = function (string) { - this.name = string || this.name; - this.version = Date.now(); + if (string != 'mouse-pointer' && string != 'pen trails' + && string != 'edge') { // used by system + this.name = string || this.name; + this.version = Date.now(); + } }; // SpriteMorph rendering @@ -1771,6 +1888,8 @@ SpriteMorph.prototype.blockTemplates = function (category) { blocks.push('-'); blocks.push(block('comeToFront')); blocks.push(block('goBack')); + blocks.push('-'); + blocks.push(block('doNotify')); // for debugging: /////////////// @@ -1799,6 +1918,11 @@ SpriteMorph.prototype.blockTemplates = function (category) { blocks.push(block('doPlaySoundUntilDone')); blocks.push(block('doStopAllSounds')); blocks.push('-'); + blocks.push(block('doSetVolume')); + blocks.push(block('doChangeVolume')); + blocks.push(watcherToggle('reportVolume')); + blocks.push(block('reportVolume')); + blocks.push('-'); blocks.push(block('doRest')); blocks.push('-'); blocks.push(block('doPlayNote')); @@ -1902,6 +2026,10 @@ SpriteMorph.prototype.blockTemplates = function (category) { blocks.push(block('reportTouchingColor')); blocks.push(block('reportColorIsTouchingColor')); blocks.push('-'); + blocks.push(block('reportCameraMotion')); + blocks.push(block('reportCameraDirection')); + blocks.push(block('reportStreamingCamera')); + blocks.push('-'); blocks.push(block('doAsk')); blocks.push(watcherToggle('getLastAnswer')); blocks.push(block('getLastAnswer')); @@ -1913,6 +2041,7 @@ SpriteMorph.prototype.blockTemplates = function (category) { blocks.push(block('reportMouseDown')); blocks.push('-'); blocks.push(block('reportKeyPressed')); + blocks.push(block('getKeysPressed')); blocks.push('-'); blocks.push(block('reportDistanceTo')); blocks.push('-'); @@ -1928,6 +2057,17 @@ SpriteMorph.prototype.blockTemplates = function (category) { blocks.push(block('doSetFastTracking')); blocks.push('-'); blocks.push(block('reportDate')); + blocks.push('-'); + blocks.push(block('reportLanguage')); + blocks.push(block('reportLocation')); + blocks.push('-'); + blocks.push(block('doVibrate')); + blocks.push('-'); + blocks.push(block('reportCompassHeading')); + blocks.push('-'); + blocks.push(block('reportAccelerationX')); + blocks.push(block('reportAccelerationY')); + blocks.push(block('reportAccelerationZ')); // for debugging: /////////////// @@ -2103,6 +2243,13 @@ SpriteMorph.prototype.blockTemplates = function (category) { blocks.push('='); + blocks.push(block('receivePeerMessage')); + blocks.push(block('sendPeerMessage')); + blocks.push(block('reportPeerId')); + blocks.push(block('reportPeerList')); + + blocks.push('='); + if (StageMorph.prototype.enableCodeMapping) { blocks.push(block('doMapCodeOrHeader')); blocks.push(block('doMapStringCode')); @@ -2620,7 +2767,8 @@ SpriteMorph.prototype.reportCostumes = function () { // SpriteMorph sound management SpriteMorph.prototype.addSound = function (audio, name) { - this.sounds.add(new Sound(audio, name)); + var volume = this.volume; + this.sounds.add(new Sound(audio, name, volume)); }; SpriteMorph.prototype.playSound = function (name) { @@ -2631,7 +2779,18 @@ SpriteMorph.prototype.playSound = function (name) { ), active; if (sound) { + sound.volume = this.volume; active = sound.play(); + + if (stage.muted === true) { + active.volume = 0; + } + + this.activeSounds.push(active); + this.activeSounds = this.activeSounds.filter(function (aud) { + return !aud.ended && !aud.terminated; + }); + if (stage) { stage.activeSounds.push(active); stage.activeSounds = stage.activeSounds.filter(function (aud) { @@ -2642,6 +2801,34 @@ SpriteMorph.prototype.playSound = function (name) { } }; +SpriteMorph.prototype.doSetVolume = function (val) { + var myself = this; + myself.volume = Math.min(Math.max(0, val), 100); + + if (myself.parentThatIsA(StageMorph).muted === true) { + return; + } + + myself.activeSounds.forEach(function (snd) { + snd.volume = myself.volume / 100; // 'audio' objects + }); +}; + +SpriteMorph.prototype.doChangeVolume = function (val) { + this.doSetVolume(this.volume + val); +}; + +SpriteMorph.prototype.reportVolume = function () { + return this.volume; +} + +SpriteMorph.prototype.unmuteAllSounds = function () { + var stage = this.parentThatIsA(StageMorph); + stage.muted = false; + + this.doSetVolume(this.volume); +}; + SpriteMorph.prototype.reportSounds = function () { return this.sounds; }; @@ -3294,9 +3481,7 @@ Morph.prototype.setPosition = function (aPoint, justMe) { // override the inherited default to make sure my parts follow // unless it's justMe var delta = aPoint.subtract(this.topLeft()); - if ((delta.x !== 0) || (delta.y !== 0)) { - this.moveBy(delta, justMe); - } + this.moveBy(delta, justMe); }; SpriteMorph.prototype.forward = function (steps) { @@ -3517,6 +3702,9 @@ SpriteMorph.prototype.allHatBlocksFor = function (message) { if (morph.selector === 'receiveOnClone') { return message === '__clone__init__'; } + if (morph.selector === 'receivePeerMessage') { + return message === '__peer__message__'; + } } return false; }); @@ -3526,7 +3714,19 @@ SpriteMorph.prototype.allHatBlocksForKey = function (key) { return this.scripts.children.filter(function (morph) { if (morph.selector) { if (morph.selector === 'receiveKey') { - return morph.inputs()[0].evaluate()[0] === key; + var selectedOption = morph.inputs()[0].evaluate()[0]; + + if (selectedOption === 'any key') { + return true; + } + if (selectedOption === 'number key' && + (key >= '0' && key <= '9')) { + return true; + } + if (selectedOption === key) { + return true; + } + return false; } } return false; @@ -3595,6 +3795,16 @@ SpriteMorph.prototype.getTempo = function () { return 0; }; +// SpriteMorph last key + +SpriteMorph.prototype.getKeysPressed = function () { + var stage = this.parentThatIsA(StageMorph); + if (stage) { + return stage.getKeysPressed(); + } + return ''; +}; + // SpriteMorph last message SpriteMorph.prototype.getLastMessage = function () { @@ -4350,6 +4560,8 @@ StageMorph.prototype.init = function (globals) { this.version = Date.now(); // for observers this.isFastTracked = false; this.cloneCount = 0; + this.volume = 100; + this.muted = false; this.timerStart = Date.now(); this.tempo = 60; // bpm @@ -4365,6 +4577,11 @@ StageMorph.prototype.init = function (globals) { this.paletteCache = {}; // not to be serialized (!) this.lastAnswer = ''; // last user input, do not persist this.activeSounds = []; // do not persist + this.acceleration = null; // do not persist + this.compassHeading = null; // do not persist + this.streamingCamera = false; + this.lastCameraCanvas = null; + this.lastCameraMotion = new Point(0, 0); this.trailsCanvas = null; this.isThreadSafe = false; @@ -4386,6 +4603,95 @@ StageMorph.prototype.init = function (globals) { this.acceptsDrops = false; this.setColor(new Color(255, 255, 255)); this.fps = this.frameRate; + + this.initPeering(); +}; + +StageMorph.prototype.initPeering = function (id) { + var myself = this; + + if (window.peers) { + // I don't know why, but it works. + window.peers.forEach(function (oldpeer) { + if (id != oldpeer.id) { + oldpeer.destroy() + } + }); + } + + this.peer = new Peer(id, { + host: 'snapmesh.herokuapp.com', + port: 443, + secure: true, + path: '/' + }); + + this.peer.on('open', function (id) { + myself.peerId = id; + }); + this.peer.on('disconnected', function () { + // peer.reconnect does not work (?) because 'id' is undefined + if (!myself.peer.destroyed) { + myself.initPeering(myself.peerId); + } + }); + this.peer.on('error', function (err) { + console.log(err); // DEBUG + }); + + this.peer.on('connection', function (connection) { + connection.on('open', function () { + connection.on('data', function (data) { + myself.newPeerMessage(data, connection.peer); + }); + }); + }); + + window.peers.push(this.peer); +}; + +StageMorph.prototype.newPeerMessage = function (data, peer) { + var ide = this.parentThatIsA(IDE_Morph); + var myself = this; + var hats = [], model, message; + + try { + model = ide.serializer.parse(data); + message = ide.serializer.loadValue(model); + + // TODO: If a Context is sent, a new Sprite appears. + // This below is just a workaround for one-level rings, + // objects should be cleaned recursively. + message.receiver = null; + message.outerContext = null; + } catch (err) { + console.log(err); // DEBUG + // Ok, it does not seem to be XML. It must be a string then. + message = data; + } + + // call hat blocks + this.children.concat(myself).forEach(function (morph) { + if (morph instanceof SpriteMorph + || morph instanceof StageMorph) { + hats = hats.concat( + morph.allHatBlocksFor('__peer__message__')); + } + }); + hats.forEach(function (block) { + var process = myself.threads.startProcess(block, + myself.isThreadSafe); + process.context.outerContext.variables.addVar(localize('message')); + process.context.outerContext.variables.setVar( + localize('message'), + message + ); + process.context.outerContext.variables.addVar(localize('peer')); + process.context.outerContext.variables.setVar( + localize('peer'), + peer + ); + }); }; // StageMorph scaling @@ -4633,6 +4939,16 @@ StageMorph.prototype.getTempo = function () { return +this.tempo; }; +// StageMorph keys + +StageMorph.prototype.getKeysPressed = function () { + var keys = []; + for (var key in this.keysPressed) { + keys.push(key); + } + return new List(keys); +}; + // StageMorph messages StageMorph.prototype.getLastMessage = function () { @@ -4686,7 +5002,7 @@ StageMorph.prototype.step = function () { world.keyboardReceiver = this; } if (world.currentKey === null) { - this.keyPressed = null; + this.keysPressed = {}; } // manage threads @@ -4874,7 +5190,7 @@ StageMorph.prototype.fireGreenFlagEvent = function () { StageMorph.prototype.fireStopAllEvent = function () { var ide = this.parentThatIsA(IDE_Morph); - this.threads.resumeAll(this.stage); + //this.threads.resumeAll(this.stage); // leads to a strange Note bug this.keysPressed = {}; this.threads.stopAll(); this.stopAllActiveSounds(); @@ -5000,6 +5316,8 @@ StageMorph.prototype.blockTemplates = function (category) { blocks.push('-'); blocks.push(block('show')); blocks.push(block('hide')); + blocks.push('-'); + blocks.push(block('doNotify')); // for debugging: /////////////// @@ -5028,6 +5346,11 @@ StageMorph.prototype.blockTemplates = function (category) { blocks.push(block('doPlaySoundUntilDone')); blocks.push(block('doStopAllSounds')); blocks.push('-'); + blocks.push(block('doSetVolume')); + blocks.push(block('doChangeVolume')); + blocks.push(watcherToggle('reportVolume')); + blocks.push(block('reportVolume')); + blocks.push('-'); blocks.push(block('doRest')); blocks.push('-'); blocks.push(block('doPlayNote')); @@ -5054,6 +5377,9 @@ StageMorph.prototype.blockTemplates = function (category) { } else if (cat === 'pen') { blocks.push(block('clear')); + blocks.push('-'); + blocks.push(block('doStreamCamera')); + blocks.push(block('doStopCamera')); } else if (cat === 'control') { @@ -5110,6 +5436,8 @@ StageMorph.prototype.blockTemplates = function (category) { } else if (cat === 'sensing') { + blocks.push(block('reportStreamingCamera')); + blocks.push('-'); blocks.push(block('doAsk')); blocks.push(watcherToggle('getLastAnswer')); blocks.push(block('getLastAnswer')); @@ -5121,6 +5449,7 @@ StageMorph.prototype.blockTemplates = function (category) { blocks.push(block('reportMouseDown')); blocks.push('-'); blocks.push(block('reportKeyPressed')); + blocks.push(block('getKeysPressed')); blocks.push('-'); blocks.push(block('doResetTimer')); blocks.push(watcherToggle('getTimer')); @@ -5134,6 +5463,17 @@ StageMorph.prototype.blockTemplates = function (category) { blocks.push(block('doSetFastTracking')); blocks.push('-'); blocks.push(block('reportDate')); + blocks.push('-'); + blocks.push(block('reportLanguage')); + blocks.push(block('reportLocation')); + blocks.push('-'); + blocks.push(block('doVibrate')); + blocks.push('-'); + blocks.push(block('reportCompassHeading')); + blocks.push('-'); + blocks.push(block('reportAccelerationX')); + blocks.push(block('reportAccelerationY')); + blocks.push(block('reportAccelerationZ')); // for debugging: /////////////// @@ -5305,6 +5645,12 @@ StageMorph.prototype.blockTemplates = function (category) { blocks.push('='); + blocks.push(block('receivePeerMessage')); + blocks.push(block('sendPeerMessage')); + blocks.push(block('reportPeerId')); + blocks.push(block('reportPeerList')); + blocks.push('='); + if (StageMorph.prototype.enableCodeMapping) { blocks.push(block('doMapCodeOrHeader')); blocks.push(block('doMapStringCode')); @@ -5563,6 +5909,15 @@ StageMorph.prototype.addSound StageMorph.prototype.playSound = SpriteMorph.prototype.playSound; +StageMorph.prototype.doSetVolume + = SpriteMorph.prototype.doSetVolume; + +StageMorph.prototype.doChangeVolume + = SpriteMorph.prototype.doChangeVolume; + +StageMorph.prototype.reportVolume + = SpriteMorph.prototype.reportVolume; + StageMorph.prototype.stopAllActiveSounds = function () { this.activeSounds.forEach(function (audio) { audio.pause(); @@ -5577,11 +5932,29 @@ StageMorph.prototype.pauseAllActiveSounds = function () { }; StageMorph.prototype.resumeAllActiveSounds = function () { + var newSounds = []; // remove Sounds that have been played so they do not resume + this.activeSounds.forEach(function (audio) { - audio.play(); + if (audio.ended === false) { + newSounds.push(audio); + audio.play(); + } }); + + this.activeSounds = newSounds; }; +StageMorph.prototype.muteAllSounds = function () { + this.muted = true; + + this.activeSounds.forEach(function (audio) { + audio.volume = 0; + }); +}; + +StageMorph.prototype.unmuteAllSounds + = SpriteMorph.prototype.unmuteAllSounds; + StageMorph.prototype.reportSounds = SpriteMorph.prototype.reportSounds; @@ -6351,9 +6724,10 @@ CostumeEditorMorph.prototype.mouseMove // Sound instance creation -function Sound(audio, name) { +function Sound(audio, name, volume) { this.audio = audio; // mandatory this.name = name || "Sound"; + this.volume = volume || 100; } Sound.prototype.play = function () { @@ -6361,6 +6735,7 @@ Sound.prototype.play = function () { // externally (i.e. by the stage) var aud = document.createElement('audio'); aud.src = this.audio.src; + aud.volume = Math.min(Math.max(0, this.volume), 100) / 100; aud.play(); return aud; }; @@ -6370,7 +6745,8 @@ Sound.prototype.copy = function () { cpy; snd.src = this.audio.src; - cpy = new Sound(snd, this.name ? copy(this.name) : null); + snd.volume = this.volume; + cpy = new Sound(snd, this.name ? copy(this.name) : null, this.volume ? copy(this.volume) : null); return cpy; }; @@ -6384,8 +6760,9 @@ Sound.prototype.toDataURL = function () { // Note instance creation -function Note(pitch) { +function Note(pitch, volume) { this.pitch = pitch === 0 ? 0 : pitch || 69; + this.volume = volume; this.setupContext(); this.oscillator = null; } @@ -6416,12 +6793,13 @@ Note.prototype.setupContext = function () { } Note.prototype.audioContext = new AudioContext(); Note.prototype.gainNode = Note.prototype.audioContext.createGain(); - Note.prototype.gainNode.gain.value = 0.25; // reduce volume by 1/4 }; // Note playing Note.prototype.play = function () { + this.gainNode.gain.value = 0.25 * this.volume / 100; // reduce volume by 1/4 + this.oscillator = this.audioContext.createOscillator(); if (!this.oscillator.start) { this.oscillator.start = this.oscillator.noteOn; @@ -6437,6 +6815,12 @@ Note.prototype.play = function () { this.oscillator.start(0); }; +Note.prototype.setVolume = function (volume) { + this.stop(); + this.volume = volume; + this.play(); +} + Note.prototype.stop = function () { if (this.oscillator) { this.oscillator.stop(0); @@ -6865,7 +7249,7 @@ WatcherMorph.prototype.object = function () { WatcherMorph.prototype.isGlobal = function (selector) { return contains( - ['getLastAnswer', 'getLastMessage', 'getTempo', 'getTimer', + ['getLastAnswer', 'getKeysPressed', 'getLastMessage', 'getTempo', 'getTimer', 'reportMouseX', 'reportMouseY', 'reportThreadCount'], selector ); |
