Switch JavaScript engine to goja

* Switch from otto to goja for JavaScript engine.
* goja supports many ES6 syntax features like arrow functions,
  const/let, for-of with more coming soon.
* Same great features as otto, more modern environment for doodads!
pull/84/head
Noah 2022-01-16 20:09:27 -08:00
parent d67c1cfcf1
commit 4d08bf1d85
42 changed files with 290 additions and 382 deletions

View File

@ -47,28 +47,24 @@ Except as contained in this notice, the name of Tavmjong Bah shall not be used i
## Go Modules ## Go Modules
### github.com/robertkrimen/otto ### github.com/dop251/goja
``` ```
Copyright (c) 2016 Dmitry Panov
Copyright (c) 2012 Robert Krimen Copyright (c) 2012 Robert Krimen
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
of this software and associated documentation files (the "Software"), to deal documentation files (the "Software"), to deal in the Software without restriction, including without limitation
in the Software without restriction, including without limitation the rights the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell permit persons to whom the Software is furnished to do so, subject to the following conditions:
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
``` ```
### github.com/satori/go.uuid ### github.com/satori/go.uuid

View File

@ -1,43 +0,0 @@
// Azulian (Red)
// DEPRECATED: they both share azulian.js now.
function main() {
var playerSpeed = 4;
var gravity = 4;
var Vx = Vy = 0;
var direction = "right";
Self.SetHitbox(0, 0, 32, 32)
Self.SetMobile(true);
Self.SetInventory(true);
Self.SetGravity(true);
Self.AddAnimation("walk-left", 100, ["red-wl1", "red-wl2", "red-wl3", "red-wl4"]);
Self.AddAnimation("walk-right", 100, ["red-wr1", "red-wr2", "red-wr3", "red-wr4"]);
// Sample our X position every few frames and detect if we've hit a solid wall.
var sampleTick = 0;
var sampleRate = 5;
var lastSampledX = 0;
setInterval(function () {
if (sampleTick % sampleRate === 0) {
var curX = Self.Position().X;
var delta = Math.abs(curX - lastSampledX);
if (delta < 5) {
direction = direction === "right" ? "left" : "right";
}
lastSampledX = curX;
}
sampleTick++;
// TODO: Vector() requires floats, pain in the butt for JS,
// the JS API should be friendlier and custom...
var Vx = parseFloat(playerSpeed * (direction === "left" ? -1 : 1));
Self.SetVelocity(Vector(Vx, 0.0));
if (!Self.IsAnimating()) {
Self.PlayAnimation("walk-" + direction, null);
}
}, 100);
}

View File

@ -5,7 +5,7 @@ var playerSpeed = 12,
lastDirection = "right"; lastDirection = "right";
function setupAnimations(color) { function setupAnimations(color) {
var left = color === 'blue' ? 'blu-wl' : 'red-wl', let left = color === 'blue' ? 'blu-wl' : 'red-wl',
right = color === 'blue' ? 'blu-wr' : 'red-wr', right = color === 'blue' ? 'blu-wr' : 'red-wr',
leftFrames = [left + '1', left + '2', left + '3', left + '4'], leftFrames = [left + '1', left + '2', left + '3', left + '4'],
rightFrames = [right + '1', right + '2', right + '3', right + '4']; rightFrames = [right + '1', right + '2', right + '3', right + '4'];
@ -15,7 +15,7 @@ function setupAnimations(color) {
} }
function main() { function main() {
var color = Self.GetTag("color"); const color = Self.GetTag("color");
playerSpeed = color === 'blue' ? 2 : 4; playerSpeed = color === 'blue' ? 2 : 4;
Self.SetMobile(true); Self.SetMobile(true);
@ -32,14 +32,14 @@ function main() {
// when it meets resistance. // when it meets resistance.
// Sample our X position every few frames and detect if we've hit a solid wall. // Sample our X position every few frames and detect if we've hit a solid wall.
var sampleTick = 0; let sampleTick = 0;
var sampleRate = 5; let sampleRate = 5;
var lastSampledX = 0; let lastSampledX = 0;
setInterval(function () { setInterval(() => {
if (sampleTick % sampleRate === 0) { if (sampleTick % sampleRate === 0) {
var curX = Self.Position().X; let curX = Self.Position().X;
var delta = Math.abs(curX - lastSampledX); let delta = Math.abs(curX - lastSampledX);
if (delta < 5) { if (delta < 5) {
direction = direction === "right" ? "left" : "right"; direction = direction === "right" ? "left" : "right";
} }
@ -47,7 +47,7 @@ function main() {
} }
sampleTick++; sampleTick++;
var Vx = parseFloat(playerSpeed * (direction === "left" ? -1 : 1)); let Vx = parseFloat(playerSpeed * (direction === "left" ? -1 : 1));
Self.SetVelocity(Vector(Vx, 0.0)); Self.SetVelocity(Vector(Vx, 0.0));
// If we changed directions, stop animating now so we can // If we changed directions, stop animating now so we can
@ -66,7 +66,7 @@ function main() {
function playerControls() { function playerControls() {
// Note: player speed is controlled by the engine. // Note: player speed is controlled by the engine.
Events.OnKeypress(function (ev) { Events.OnKeypress((ev) => {
if (ev.Right) { if (ev.Right) {
if (!Self.IsAnimating()) { if (!Self.IsAnimating()) {
Self.PlayAnimation("walk-right", null); Self.PlayAnimation("walk-right", null);

View File

@ -1,17 +1,17 @@
// Bird // Bird
function main() { function main() {
var speed = 4; let speed = 4,
var Vx = Vy = 0; Vx = Vy = 0,
var altitude = Self.Position().Y; // original height in the level altitude = Self.Position().Y; // original height in the level
var direction = "left", let direction = "left",
lastDirection = "left"; lastDirection = "left";
var states = { let states = {
flying: 0, flying: 0,
diving: 1, diving: 1,
}; };
var state = states.flying; let state = states.flying;
Self.SetMobile(true); Self.SetMobile(true);
Self.SetGravity(false); Self.SetGravity(false);
@ -24,22 +24,22 @@ function main() {
return player(); return player();
} }
Events.OnCollide(function (e) { Events.OnCollide((e) => {
if (e.Actor.IsMobile() && e.InHitbox) { if (e.Actor.IsMobile() && e.InHitbox) {
return false; return false;
} }
}); });
// Sample our X position every few frames and detect if we've hit a solid wall. // Sample our X position every few frames and detect if we've hit a solid wall.
var sampleTick = 0; let sampleTick = 0,
var sampleRate = 2; sampleRate = 2,
var lastSampledX = 0; lastSampledX = 0,
var lastSampledY = 0; lastSampledY = 0;
setInterval(function () { setInterval(() => {
if (sampleTick % sampleRate === 0) { if (sampleTick % sampleRate === 0) {
var curX = Self.Position().X; let curX = Self.Position().X;
var delta = Math.abs(curX - lastSampledX); let delta = Math.abs(curX - lastSampledX);
if (delta < 5) { if (delta < 5) {
direction = direction === "right" ? "left" : "right"; direction = direction === "right" ? "left" : "right";
} }
@ -48,15 +48,15 @@ function main() {
sampleTick++; sampleTick++;
// If we are not flying at our original altitude, correct for that. // If we are not flying at our original altitude, correct for that.
var curV = Self.Position(); let curV = Self.Position();
var Vy = 0.0; let Vy = 0.0;
if (curV.Y != altitude) { if (curV.Y != altitude) {
Vy = curV.Y < altitude ? 1 : -1; Vy = curV.Y < altitude ? 1 : -1;
} }
// TODO: Vector() requires floats, pain in the butt for JS, // TODO: Vector() requires floats, pain in the butt for JS,
// the JS API should be friendlier and custom... // the JS API should be friendlier and custom...
var Vx = parseFloat(speed * (direction === "left" ? -1 : 1)); let Vx = parseFloat(speed * (direction === "left" ? -1 : 1));
Self.SetVelocity(Vector(Vx, Vy)); Self.SetVelocity(Vector(Vx, Vy));
// If we changed directions, stop animating now so we can // If we changed directions, stop animating now so we can
@ -76,7 +76,7 @@ function main() {
// If under control of the player character. // If under control of the player character.
function player() { function player() {
Self.SetInventory(true); Self.SetInventory(true);
Events.OnKeypress(function (ev) { Events.OnKeypress((ev) => {
Vx = 0; Vx = 0;
Vy = 0; Vy = 0;

View File

@ -1,20 +1,21 @@
// Pushable Box. // Pushable Box.
var speed = 4;
var size = 75; const speed = 4,
size = 75;
function main() { function main() {
Self.SetMobile(true); Self.SetMobile(true);
Self.SetGravity(true); Self.SetGravity(true);
Self.SetHitbox(0, 0, size, size); Self.SetHitbox(0, 0, size, size);
Events.OnCollide(function (e) { Events.OnCollide((e) => {
// Ignore events from neighboring Boxes. // Ignore events from neighboring Boxes.
if (e.Actor.Actor.Filename === "box.doodad") { if (e.Actor.Actor.Filename === "box.doodad") {
return false; return false;
} }
if (e.Actor.IsMobile() && e.InHitbox) { if (e.Actor.IsMobile() && e.InHitbox) {
var overlap = e.Overlap; let overlap = e.Overlap;
if (overlap.Y === 0 && !(overlap.X === 0 && overlap.W < 5) && !(overlap.X === size)) { if (overlap.Y === 0 && !(overlap.X === 0 && overlap.W < 5) && !(overlap.X === size)) {
// Standing on top, ignore. // Standing on top, ignore.
@ -39,8 +40,8 @@ function main() {
}); });
// When we receive power, we reset to our original position. // When we receive power, we reset to our original position.
var origPoint = Self.Position(); let origPoint = Self.Position();
Message.Subscribe("power", function (powered) { Message.Subscribe("power", (powered) => {
Self.MoveTo(origPoint); Self.MoveTo(origPoint);
Self.SetVelocity(Vector(0, 0)); Self.SetVelocity(Vector(0, 0));
}); });
@ -52,8 +53,8 @@ function main() {
function animate() { function animate() {
Self.AddAnimation("animate", 100, [0, 1, 2, 3, 2, 1]); Self.AddAnimation("animate", 100, [0, 1, 2, 3, 2, 1]);
var running = false; let running = false;
setInterval(function () { setInterval(() => {
if (!running) { if (!running) {
running = true; running = true;
Self.PlayAnimation("animate", function () { Self.PlayAnimation("animate", function () {

View File

@ -1,10 +1,10 @@
const playerSpeed = 12;
let Vx = Vy = 0,
animating = false,
animStart = animEnd = 0;
function main() { function main() {
var playerSpeed = 12;
var Vx = Vy = 0;
var animating = false;
var animStart = animEnd = 0;
Self.SetMobile(true); Self.SetMobile(true);
Self.SetInventory(true); Self.SetInventory(true);
Self.SetGravity(true); Self.SetGravity(true);
@ -13,13 +13,13 @@ function main() {
Self.AddAnimation("walk-right", 200, ["stand-right", "walk-right-1", "walk-right-2", "walk-right-3", "walk-right-2", "walk-right-1"]); Self.AddAnimation("walk-right", 200, ["stand-right", "walk-right-1", "walk-right-2", "walk-right-3", "walk-right-2", "walk-right-1"]);
// If the player suddenly changes direction, reset the animation state to quickly switch over. // If the player suddenly changes direction, reset the animation state to quickly switch over.
var lastVelocity = Vector(0, 0); let lastVelocity = Vector(0, 0);
Events.OnKeypress(function (ev) { Events.OnKeypress((ev) => {
Vx = 0; Vx = 0;
Vy = 0; Vy = 0;
var curVelocity = Self.GetVelocity(); let curVelocity = Self.GetVelocity();
if ((lastVelocity.X < 0 && curVelocity.X > 0) || if ((lastVelocity.X < 0 && curVelocity.X > 0) ||
(lastVelocity.X > 0 && curVelocity.X < 0)) { (lastVelocity.X > 0 && curVelocity.X < 0)) {
Self.StopAnimation(); Self.StopAnimation();
@ -40,7 +40,5 @@ function main() {
Self.StopAnimation(); Self.StopAnimation();
animating = false; animating = false;
} }
// Self.SetVelocity(Point(Vx, Vy));
}) })
} }

View File

@ -1,18 +1,18 @@
function main() { function main() {
var timer = 0; let timer = 0;
var pressed = false; let pressed = false;
// Has a linked Sticky Button been pressed permanently down? // Has a linked Sticky Button been pressed permanently down?
var stickyDown = false; let stickyDown = false;
Message.Subscribe("sticky:down", function (down) { Message.Subscribe("sticky:down", (down) => {
stickyDown = down; stickyDown = down;
Self.ShowLayer(stickyDown ? 1 : 0); Self.ShowLayer(stickyDown ? 1 : 0);
}); });
// Track who all is colliding with us. // Track who all is colliding with us.
var colliders = {}; let colliders = {};
Events.OnCollide(function (e) { Events.OnCollide((e) => {
if (!e.Settled) { if (!e.Settled) {
return; return;
} }
@ -46,7 +46,7 @@ function main() {
Self.ShowLayer(1); Self.ShowLayer(1);
}); });
Events.OnLeave(function (e) { Events.OnLeave((e) => {
delete colliders[e.Actor.ID()]; delete colliders[e.Actor.ID()];
if (Object.keys(colliders).length === 0 && !stickyDown) { if (Object.keys(colliders).length === 0 && !stickyDown) {

View File

@ -1,8 +1,8 @@
function main() { function main() {
var pressed = false; let pressed = false;
// When a sticky button receives power, it pops back up. // When a sticky button receives power, it pops back up.
Message.Subscribe("power", function (powered) { Message.Subscribe("power", (powered) => {
if (powered && pressed) { if (powered && pressed) {
Self.ShowLayer(0); Self.ShowLayer(0);
pressed = false; pressed = false;
@ -12,7 +12,7 @@ function main() {
} }
}) })
Events.OnCollide(function (e) { Events.OnCollide((e) => {
if (!e.Settled) { if (!e.Settled) {
return; return;
} }

View File

@ -6,19 +6,16 @@ function main() {
Self.AddAnimation("fall", 100, ["fall1", "fall2", "fall3", "fall4"]); Self.AddAnimation("fall", 100, ["fall1", "fall2", "fall3", "fall4"]);
// Recover time for the floor to respawn. // Recover time for the floor to respawn.
var recover = 5000; let recover = 5000;
// States of the floor. // States of the floor.
var stateSolid = 0; let stateSolid = 0;
var stateShaking = 1; let stateShaking = 1;
var stateFalling = 2; let stateFalling = 2;
var stateFallen = 3; let stateFallen = 3;
var state = stateSolid; let state = stateSolid;
// Started the animation? Events.OnCollide((e) => {
var startedAnimation = false;
Events.OnCollide(function(e) {
// If the floor is falling, the player passes right thru. // If the floor is falling, the player passes right thru.
if (state === stateFalling || state === stateFallen) { if (state === stateFalling || state === stateFallen) {
@ -40,15 +37,15 @@ function main() {
// Begin the animation sequence if we're in the solid state. // Begin the animation sequence if we're in the solid state.
if (state === stateSolid) { if (state === stateSolid) {
state = stateShaking; state = stateShaking;
Self.PlayAnimation("shake", function() { Self.PlayAnimation("shake", () => {
state = stateFalling; state = stateFalling;
Self.PlayAnimation("fall", function() { Self.PlayAnimation("fall", () => {
Sound.Play("crumbly-break.wav") Sound.Play("crumbly-break.wav")
state = stateFallen; state = stateFallen;
Self.ShowLayerNamed("fallen"); Self.ShowLayerNamed("fallen");
// Recover after a while. // Recover after a while.
setTimeout(function() { setTimeout(() => {
Self.ShowLayer(0); Self.ShowLayer(0);
state = stateSolid; state = stateSolid;
}, recover); }, recover);

View File

@ -1,9 +1,11 @@
function main() { // Colored Locked Doors.
var color = Self.GetTag("color");
var keyname = color === "small" ? "small-key.doodad" : "key-" + color + ".doodad";
const color = Self.GetTag("color"),
keyname = color === "small" ? "small-key.doodad" : "key-" + color + ".doodad";
function main() {
// Layers in the doodad image. // Layers in the doodad image.
var layer = { let layer = {
closed: 0, closed: 0,
unlocked: 1, unlocked: 1,
right: 2, right: 2,
@ -11,13 +13,13 @@ function main() {
}; };
// Variables that change in event handler. // Variables that change in event handler.
var unlocked = false; // Key has been used to unlock the door (one time). let unlocked = false; // Key has been used to unlock the door (one time).
var opened = false; // If door is currently showing its opened state. let opened = false; // If door is currently showing its opened state.
var enterSide = 0; // Side of player entering the door, -1 or 1, left or right. let enterSide = 0; // Side of player entering the door, -1 or 1, left or right.
Self.SetHitbox(34, 0, 13, 76); Self.SetHitbox(34, 0, 13, 76);
Events.OnCollide(function(e) { Events.OnCollide((e) => {
// Record the side that this actor has touched us, in case the door // Record the side that this actor has touched us, in case the door
// needs to open. // needs to open.
if (enterSide === 0) { if (enterSide === 0) {
@ -37,7 +39,7 @@ function main() {
} }
// Do they have our key? // Do they have our key?
var hasKey = e.Actor.HasItem(keyname) >= 0; let hasKey = e.Actor.HasItem(keyname) >= 0;
if (!hasKey) { if (!hasKey) {
// Door is locked. // Door is locked.
return false; return false;
@ -55,7 +57,7 @@ function main() {
} }
} }
}); });
Events.OnLeave(function(e) { Events.OnLeave((e) => {
Self.ShowLayer(unlocked ? layer.unlocked : layer.closed); Self.ShowLayer(unlocked ? layer.unlocked : layer.closed);
// Sound.Play("door-close.wav") // Sound.Play("door-close.wav")

View File

@ -1,6 +1,8 @@
var animating = false; // Electric Door
var opened = false;
var powerState = false; let animating = false;
let opened = false;
let powerState = false;
// Function to handle the door opening or closing. // Function to handle the door opening or closing.
function setPoweredState(powered) { function setPoweredState(powered) {
@ -13,14 +15,14 @@ function setPoweredState(powered) {
animating = true; animating = true;
Sound.Play("electric-door.wav") Sound.Play("electric-door.wav")
Self.PlayAnimation("open", function () { Self.PlayAnimation("open", () => {
opened = true; opened = true;
animating = false; animating = false;
}); });
} else { } else {
animating = true; animating = true;
Sound.Play("electric-door.wav") Sound.Play("electric-door.wav")
Self.PlayAnimation("close", function () { Self.PlayAnimation("close", () => {
opened = false; opened = false;
animating = false; animating = false;
}) })
@ -31,7 +33,6 @@ function main() {
Self.AddAnimation("open", 100, [0, 1, 2, 3]); Self.AddAnimation("open", 100, [0, 1, 2, 3]);
Self.AddAnimation("close", 100, [3, 2, 1, 0]); Self.AddAnimation("close", 100, [3, 2, 1, 0]);
Self.SetHitbox(0, 0, 34, 76); Self.SetHitbox(0, 0, 34, 76);
// A linked Switch that activates the door will send the Toggle signal // A linked Switch that activates the door will send the Toggle signal
@ -39,13 +40,13 @@ function main() {
// state on this signal, and ignore the very next Power signal. Ordinary // state on this signal, and ignore the very next Power signal. Ordinary
// power sources like Buttons will work as normal, as they emit only a power // power sources like Buttons will work as normal, as they emit only a power
// signal. // signal.
var ignoreNextPower = false; let ignoreNextPower = false;
Message.Subscribe("switch:toggle", function (powered) { Message.Subscribe("switch:toggle", (powered) => {
ignoreNextPower = true; ignoreNextPower = true;
setPoweredState(!powerState); setPoweredState(!powerState);
}) })
Message.Subscribe("power", function (powered) { Message.Subscribe("power", (powered) => {
if (ignoreNextPower) { if (ignoreNextPower) {
ignoreNextPower = false; ignoreNextPower = false;
return; return;
@ -54,7 +55,7 @@ function main() {
setPoweredState(powered); setPoweredState(powered);
}); });
Events.OnCollide(function (e) { Events.OnCollide((e) => {
if (e.InHitbox) { if (e.InHitbox) {
if (!opened) { if (!opened) {
return false; return false;

View File

@ -1,8 +1,10 @@
function main() { // Colored Keys and Small Key
var color = Self.GetTag("color");
var quantity = color === "small" ? 1 : 0;
Events.OnCollide(function (e) { const color = Self.GetTag("color"),
quantity = color === "small" ? 1 : 0;
function main() {
Events.OnCollide((e) => {
if (e.Settled) { if (e.Settled) {
if (e.Actor.HasInventory()) { if (e.Actor.HasInventory()) {
// If we don't have a quantity, and the actor already has // If we don't have a quantity, and the actor already has

View File

@ -1,27 +0,0 @@
// DEPRECATED: old locked door script. Superceded by colored-door.js.
function main() {
Self.AddAnimation("open", 0, [1]);
var unlocked = false;
var color = Self.GetTag("color");
Self.SetHitbox(16, 0, 32, 64);
Events.OnCollide(function(e) {
if (unlocked) {
return;
}
if (e.InHitbox) {
var data = e.Actor.GetData("key:" + color);
if (data === "") {
// Door is locked.
return false;
}
if (e.Settled) {
unlocked = true;
Self.PlayAnimation("open", null);
}
}
});
}

View File

@ -8,9 +8,9 @@ function main() {
Self.SetGravity(true); Self.SetGravity(true);
// Monitor our Y position to tell if we've been falling. // Monitor our Y position to tell if we've been falling.
var lastPoint = Self.Position(); let lastPoint = Self.Position();
setInterval(function () { setInterval(() => {
var nowAt = Self.Position(); let nowAt = Self.Position();
if (nowAt.Y > lastPoint.Y) { if (nowAt.Y > lastPoint.Y) {
falling = true; falling = true;
} else { } else {
@ -19,7 +19,7 @@ function main() {
lastPoint = nowAt; lastPoint = nowAt;
}, 100); }, 100);
Events.OnCollide(function (e) { Events.OnCollide((e) => {
if (!e.Settled) { if (!e.Settled) {
return; return;
} }
@ -43,8 +43,8 @@ function main() {
}); });
// When we receive power, we reset to our original position. // When we receive power, we reset to our original position.
var origPoint = Self.Position(); let origPoint = Self.Position();
Message.Subscribe("power", function (powered) { Message.Subscribe("power", (powered) => {
Self.MoveTo(origPoint); Self.MoveTo(origPoint);
Self.SetVelocity(Vector(0, 0)); Self.SetVelocity(Vector(0, 0));
}); });

View File

@ -7,11 +7,11 @@ function main() {
// Checkpoints broadcast to all of their peers so they all // Checkpoints broadcast to all of their peers so they all
// know which one is the most recently activated. // know which one is the most recently activated.
Message.Subscribe("broadcast:checkpoint", function (currentID) { Message.Subscribe("broadcast:checkpoint", (currentID) => {
setActive(false); setActive(false);
}); });
Events.OnCollide(function (e) { Events.OnCollide((e) => {
if (!e.Settled) { if (!e.Settled) {
return; return;
} }

View File

@ -2,7 +2,7 @@
function main() { function main() {
Self.SetHitbox(22 + 16, 16, 75 - 16, 86); Self.SetHitbox(22 + 16, 16, 75 - 16, 86);
Events.OnCollide(function (e) { Events.OnCollide((e) => {
if (!e.Settled) { if (!e.Settled) {
return; return;
} }

View File

@ -4,8 +4,7 @@ function main() {
// Linking a doodad to the Start Flag sets the // Linking a doodad to the Start Flag sets the
// player character. Destroy the original doodads. // player character. Destroy the original doodads.
var links = Self.GetLinks(); for (var link of Self.GetLinks()) {
for (var i = 0; i < links.length; i++) { link.Destroy();
links[i].Destroy();
} }
} }

View File

@ -3,9 +3,9 @@ function main() {
Self.SetHitbox(0, 0, 42, 42); Self.SetHitbox(0, 0, 42, 42);
// Blue block is ON by default. // Blue block is ON by default.
var state = true; let state = true;
Message.Subscribe("broadcast:state-change", function(newState) { Message.Subscribe("broadcast:state-change", (newState) => {
state = !newState; state = !newState;
// Layer 0: ON // Layer 0: ON
@ -13,7 +13,7 @@ function main() {
Self.ShowLayer(state ? 0 : 1); Self.ShowLayer(state ? 0 : 1);
}); });
Events.OnCollide(function(e) { Events.OnCollide((e) => {
if (e.Actor.IsMobile() && e.InHitbox) { if (e.Actor.IsMobile() && e.InHitbox) {
if (state) { if (state) {
return false; return false;

View File

@ -3,9 +3,9 @@ function main() {
Self.SetHitbox(0, 0, 42, 42); Self.SetHitbox(0, 0, 42, 42);
// Orange block is OFF by default. // Orange block is OFF by default.
var state = false; let state = false;
Message.Subscribe("broadcast:state-change", function(newState) { Message.Subscribe("broadcast:state-change", (newState) => {
state = newState; state = newState;
// Layer 0: OFF // Layer 0: OFF
@ -13,7 +13,7 @@ function main() {
Self.ShowLayer(state ? 1 : 0); Self.ShowLayer(state ? 1 : 0);
}); });
Events.OnCollide(function(e) { Events.OnCollide((e) => {
if (e.Actor.IsMobile() && e.InHitbox) { if (e.Actor.IsMobile() && e.InHitbox) {
if (state) { if (state) {
return false; return false;

View File

@ -1,23 +1,23 @@
// State Block Control Button // State Block Control Button
// Button is "OFF" by default. // Button is "OFF" by default.
var state = false; let state = false;
function main() { function main() {
Self.SetHitbox(0, 0, 42, 42); Self.SetHitbox(0, 0, 42, 42);
// When the button is activated, don't keep toggling state until we're not // When the button is activated, don't keep toggling state until we're not
// being touched again. // being touched again.
var colliding = false; let colliding = false;
// If we receive a state change event from a DIFFERENT on/off button, update // If we receive a state change event from a DIFFERENT on/off button, update
// ourself to match the state received. // ourself to match the state received.
Message.Subscribe("broadcast:state-change", function(value) { Message.Subscribe("broadcast:state-change", (value) => {
state = value; state = value;
showSprite(); showSprite();
}); });
Events.OnCollide(function(e) { Events.OnCollide((e) => {
if (colliding) { if (colliding) {
return false; return false;
} }
@ -40,7 +40,7 @@ function main() {
return false; return false;
}); });
Events.OnLeave(function(e) { Events.OnLeave((e) => {
colliding = false; colliding = false;
}) })
} }

View File

@ -8,11 +8,11 @@ function main() {
// Checkpoints broadcast to all of their peers so they all // Checkpoints broadcast to all of their peers so they all
// know which one is the most recently activated. // know which one is the most recently activated.
Message.Subscribe("broadcast:checkpoint", function (currentID) { Message.Subscribe("broadcast:checkpoint", (currentID) => {
setActive(false); setActive(false);
}); });
Events.OnCollide(function (e) { Events.OnCollide((e) => {
if (isCurrentCheckpoint || !e.Settled) { if (isCurrentCheckpoint || !e.Settled) {
return; return;
} }

View File

@ -2,7 +2,7 @@
function main() { function main() {
Self.Hide(); Self.Hide();
Events.OnCollide(function (e) { Events.OnCollide((e) => {
if (!e.Settled) { if (!e.Settled) {
return; return;
} }

View File

@ -2,7 +2,7 @@
function main() { function main() {
Self.Hide(); Self.Hide();
Events.OnCollide(function (e) { Events.OnCollide((e) => {
if (!e.Settled) { if (!e.Settled) {
return; return;
} }

View File

@ -15,7 +15,7 @@ function main() {
); );
} }
Message.Subscribe("broadcast:ready", function () { Message.Subscribe("broadcast:ready", () => {
Message.Publish("switch:toggle", true); Message.Publish("switch:toggle", true);
Message.Publish("power", true); Message.Publish("power", true);
}); });

View File

@ -4,7 +4,7 @@
function main() { function main() {
Self.Hide(); Self.Hide();
var active = true, let active = true,
timeout = 250, timeout = 250,
ms = Self.GetTag("ms"); ms = Self.GetTag("ms");
@ -12,7 +12,7 @@ function main() {
timeout = parseInt(ms); timeout = parseInt(ms);
} }
Events.OnCollide(function (e) { Events.OnCollide((e) => {
if (!active || !e.Settled) { if (!active || !e.Settled) {
return; return;
} }
@ -25,7 +25,7 @@ function main() {
if (e.InHitbox) { if (e.InHitbox) {
// Grab hold of the player. // Grab hold of the player.
e.Actor.Freeze(); e.Actor.Freeze();
setTimeout(function () { setTimeout(() => {
e.Actor.Unfreeze(); e.Actor.Unfreeze();
}, timeout); }, timeout);
@ -34,7 +34,7 @@ function main() {
}); });
// Reset the trap if powered by a button. // Reset the trap if powered by a button.
Message.Subscribe("power", function (powered) { Message.Subscribe("power", (powered) => {
active = true; active = true;
}); });
} }

View File

@ -4,15 +4,15 @@ function main() {
// 0: Off // 0: Off
// 1: On // 1: On
var state = false; let state = false;
var collide = false; let collide = false;
Message.Subscribe("power", function (powered) { Message.Subscribe("power", (powered) => {
state = powered; state = powered;
showState(state); showState(state);
}); });
Events.OnCollide(function (e) { Events.OnCollide((e) => {
if (!e.Settled || !e.Actor.IsMobile()) { if (!e.Settled || !e.Actor.IsMobile()) {
return; return;
} }
@ -21,7 +21,6 @@ function main() {
Sound.Play("button-down.wav") Sound.Play("button-down.wav")
state = !state; state = !state;
var nonce = Math.random() * 2147483647;
Message.Publish("switch:toggle", state); Message.Publish("switch:toggle", state);
Message.Publish("power", state); Message.Publish("power", state);
showState(state); showState(state);
@ -30,7 +29,7 @@ function main() {
} }
}); });
Events.OnLeave(function (e) { Events.OnLeave((e) => {
collide = false; collide = false;
}); });
} }

View File

@ -21,8 +21,8 @@ function main() {
// Common "steal" power between playable and A.I. thieves. // Common "steal" power between playable and A.I. thieves.
function stealable() { function stealable() {
// Steals your items. // Steals your items.
Events.OnCollide(function (e) { Events.OnCollide((e) => {
var victim = e.Actor; let victim = e.Actor;
if (!e.Settled) { if (!e.Settled) {
return; return;
} }
@ -33,17 +33,17 @@ function stealable() {
} }
// Steal inventory // Steal inventory
var stolen = 0; let stolen = 0;
if (victim.HasInventory()) { if (victim.HasInventory()) {
var myInventory = Self.Inventory(), let myInventory = Self.Inventory(),
theirInventory = victim.Inventory(); theirInventory = victim.Inventory();
for (var key in theirInventory) { for (let key in theirInventory) {
if (!theirInventory.hasOwnProperty(key)) { if (!theirInventory.hasOwnProperty(key)) {
continue; continue;
} }
var value = theirInventory[key]; let value = theirInventory[key];
if (value > 0 || myInventory[key] === undefined) { if (value > 0 || myInventory[key] === undefined) {
victim.RemoveItem(key, value); victim.RemoveItem(key, value);
Self.AddItem(key, value); Self.AddItem(key, value);
@ -68,7 +68,7 @@ function stealable() {
// when it encounters and obstacle. // when it encounters and obstacle.
function ai() { function ai() {
// Walks back and forth. // Walks back and forth.
var Vx = Vy = 0.0, let Vx = Vy = 0.0,
playerSpeed = 4, playerSpeed = 4,
direction = "right", direction = "right",
lastDirection = "right", lastDirection = "right",
@ -76,9 +76,9 @@ function ai() {
sampleTick = 0, sampleTick = 0,
sampleRate = 2; sampleRate = 2;
setInterval(function () { setInterval(() => {
if (sampleTick % sampleRate === 0) { if (sampleTick % sampleRate === 0) {
var curX = Self.Position().X, let curX = Self.Position().X,
delta = Math.abs(curX - lastSampledX); delta = Math.abs(curX - lastSampledX);
if (delta < 5) { if (delta < 5) {
direction = direction === "right" ? "left" : "right"; direction = direction === "right" ? "left" : "right";
@ -106,7 +106,7 @@ function ai() {
// If under control of the player character. // If under control of the player character.
function playable() { function playable() {
Events.OnKeypress(function (ev) { Events.OnKeypress((ev) => {
Vx = 0; Vx = 0;
Vy = 0; Vy = 0;

View File

@ -1,36 +0,0 @@
function main() {
var timer = 0;
Self.SetHitbox(0, 0, 72, 6);
var animationSpeed = 100;
var opened = false;
Self.AddAnimation("open", animationSpeed, ["down1", "down2", "down3", "down4"]);
Self.AddAnimation("close", animationSpeed, ["down4", "down3", "down2", "down1"]);
Events.OnCollide( function(e) {
if (opened) {
return;
}
// Is the actor colliding our solid part?
if (e.InHitbox) {
// Touching the top or the bottom?
if (e.Overlap.Y > 0) {
return false; // solid wall when touched from below
} else {
opened = true;
Self.PlayAnimation("open", function() {
});
}
}
});
Events.OnLeave(function() {
if (opened) {
Self.PlayAnimation("close", function() {
opened = false;
});
}
})
}

View File

@ -1,12 +1,12 @@
function main() { // Trapdoors.
// What direction is the trapdoor facing? // What direction is the trapdoor facing?
var direction = Self.GetTag("direction"); const direction = Self.GetTag("direction");
var timer = 0;
function main() {
// Set our hitbox based on our orientation. // Set our hitbox based on our orientation.
var thickness = 10; let thickness = 10;
var doodadSize = 86; let doodadSize = 86;
if (direction === "left") { if (direction === "left") {
Self.SetHitbox(48, 0, doodadSize, doodadSize); Self.SetHitbox(48, 0, doodadSize, doodadSize);
} else if (direction === "right") { } else if (direction === "right") {
@ -17,12 +17,12 @@ function main() {
Self.SetHitbox(0, 0, doodadSize, thickness); Self.SetHitbox(0, 0, doodadSize, thickness);
} }
var animationSpeed = 100; let animationSpeed = 100;
var opened = false; let opened = false;
// Register our animations. // Register our animations.
var frames = []; let frames = [];
for (var i = 1; i <= 4; i++) { for (let i = 1; i <= 4; i++) {
frames.push(direction + i); frames.push(direction + i);
} }
@ -30,7 +30,7 @@ function main() {
frames.reverse(); frames.reverse();
Self.AddAnimation("close", animationSpeed, frames); Self.AddAnimation("close", animationSpeed, frames);
Events.OnCollide( function(e) { Events.OnCollide((e) => {
if (opened) { if (opened) {
return; return;
} }
@ -78,9 +78,9 @@ function main() {
} }
}); });
Events.OnLeave(function() { Events.OnLeave(() => {
if (opened) { if (opened) {
Self.PlayAnimation("close", function() { Self.PlayAnimation("close", () => {
opened = false; opened = false;
}); });
} }

View File

@ -1,16 +1,17 @@
// Warp Doors // Warp Doors
const color = Self.GetTag("color"),
isStateDoor = color === 'blue' || color === 'orange';
// State in case we're a blue warp door.
let state = color === 'blue',
animating = false,
collide = false;
function main() { function main() {
// Are we a blue or orange door? Regular warp door will be 'none'
var color = Self.GetTag("color");
var isStateDoor = color === 'blue' || color === 'orange';
var state = color === 'blue'; // Blue door is ON by default.
var animating = false;
var collide = false;
// Declare animations and sprite names. // Declare animations and sprite names.
var animSpeed = 100; let animSpeed = 100;
var spriteDefault, spriteDisabled; // the latter for state doors. let spriteDefault, spriteDisabled; // the latter for state doors.
if (color === 'blue') { if (color === 'blue') {
Self.AddAnimation("open", animSpeed, ["blue-2", "blue-3", "blue-4"]); Self.AddAnimation("open", animSpeed, ["blue-2", "blue-3", "blue-4"]);
Self.AddAnimation("close", animSpeed, ["blue-4", "blue-3", "blue-2", "blue-1"]); Self.AddAnimation("close", animSpeed, ["blue-4", "blue-3", "blue-2", "blue-1"]);
@ -33,17 +34,17 @@ function main() {
} }
// Find our linked Warp Door. // Find our linked Warp Door.
var links = Self.GetLinks() let linkedDoor = null;
var linkedDoor = null; for (let link of Self.GetLinks()) {
for (var i = 0; i < links.length; i++) { if (link.Title.indexOf("Warp Door") > -1) {
if (links[i].Title.indexOf("Warp Door") > -1) { linkedDoor = link;
linkedDoor = links[i]; break;
} }
} }
// Subscribe to the global state-change if we are a state door. // Subscribe to the global state-change if we are a state door.
if (isStateDoor) { if (isStateDoor) {
Message.Subscribe("broadcast:state-change", function(newState) { Message.Subscribe("broadcast:state-change", (newState) => {
state = color === 'blue' ? !newState : newState; state = color === 'blue' ? !newState : newState;
// Activate or deactivate the door. // Activate or deactivate the door.
@ -52,12 +53,11 @@ function main() {
} }
// For player groundedness work-around // For player groundedness work-around
var playerLastY = []; // last sampling of Y values let playerLastY = []; // last sampling of Y values
var lastUsed = time.Now();
// The player Uses the door. // The player Uses the door.
var flashedCooldown = false; // "Locked Door" flashed message. let flashedCooldown = false; // "Locked Door" flashed message.
Events.OnUse(function(e) { Events.OnUse((e) => {
if (animating) { if (animating) {
return; return;
} }
@ -86,7 +86,7 @@ function main() {
// Work-around: if two Boxes are stacked atop each other the player can // Work-around: if two Boxes are stacked atop each other the player can
// get stuck if he jumps on top. He may not be Grounded but isn't changing // get stuck if he jumps on top. He may not be Grounded but isn't changing
// effective Y position and a warp door may work as a good way out. // effective Y position and a warp door may work as a good way out.
var yValue = e.Actor.Position().Y; let yValue = e.Actor.Position().Y;
// Collect a sampling of last few Y values. If the player Y position // Collect a sampling of last few Y values. If the player Y position
// is constant the last handful of frames, treat them as if they're // is constant the last handful of frames, treat them as if they're
@ -100,8 +100,8 @@ function main() {
playerLastY.pop(); playerLastY.pop();
// Hasn't moved? // Hasn't moved?
var isGrounded = true; let isGrounded = true;
for (var i = 0; i < playerLastY.length; i++) { for (let i = 0; i < playerLastY.length; i++) {
if (yValue !== playerLastY[i]) { if (yValue !== playerLastY[i]) {
isGrounded = false; isGrounded = false;
break; break;
@ -120,9 +120,9 @@ function main() {
// Play the open and close animation. // Play the open and close animation.
animating = true; animating = true;
Self.PlayAnimation("open", function() { Self.PlayAnimation("open", () => {
e.Actor.Hide() e.Actor.Hide()
Self.PlayAnimation("close", function() { Self.PlayAnimation("close", () => {
Self.ShowLayerNamed(isStateDoor && !state ? spriteDisabled : spriteDefault); Self.ShowLayerNamed(isStateDoor && !state ? spriteDisabled : spriteDefault);
animating = false; animating = false;
@ -139,12 +139,12 @@ function main() {
}); });
// Respond to incoming warp events. // Respond to incoming warp events.
Message.Subscribe("warp-door:incoming", function(player) { Message.Subscribe("warp-door:incoming", (player) => {
animating = true; animating = true;
player.Unfreeze(); player.Unfreeze();
Self.PlayAnimation("open", function() { Self.PlayAnimation("open", () => {
player.Show(); player.Show();
Self.PlayAnimation("close", function() { Self.PlayAnimation("close", () => {
animating = false; animating = false;
// If the receiving door was a State Door, fix its state. // If the receiving door was a State Door, fix its state.

3
go.mod
View File

@ -10,12 +10,13 @@ require (
github.com/aichaos/rivescript-go v0.3.1 github.com/aichaos/rivescript-go v0.3.1
github.com/cpuguy83/go-md2man/v2 v2.0.1 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.1 // indirect
github.com/dgrijalva/jwt-go v3.2.0+incompatible github.com/dgrijalva/jwt-go v3.2.0+incompatible
github.com/dop251/goja v0.0.0-20220110113543-261677941f3c
github.com/fsnotify/fsnotify v1.4.9 github.com/fsnotify/fsnotify v1.4.9
github.com/gen2brain/dlgs v0.0.0-20211108104213-bade24837f0b github.com/gen2brain/dlgs v0.0.0-20211108104213-bade24837f0b
github.com/google/uuid v1.3.0 github.com/google/uuid v1.3.0
github.com/gopherjs/gopherjs v0.0.0-20220104163920-15ed2e8cf2bd // indirect github.com/gopherjs/gopherjs v0.0.0-20220104163920-15ed2e8cf2bd // indirect
github.com/kirsle/configdir v0.0.0-20170128060238-e45d2f54772f github.com/kirsle/configdir v0.0.0-20170128060238-e45d2f54772f
github.com/robertkrimen/otto v0.0.0-20211024170158-b87d35c0b86f github.com/robertkrimen/otto v0.0.0-20211024170158-b87d35c0b86f // indirect
github.com/tomnomnom/xtermcolor v0.0.0-20160428124646-b78803f00a7e // indirect github.com/tomnomnom/xtermcolor v0.0.0-20160428124646-b78803f00a7e // indirect
github.com/urfave/cli/v2 v2.3.0 github.com/urfave/cli/v2 v2.3.0
github.com/veandco/go-sdl2 v0.4.10 github.com/veandco/go-sdl2 v0.4.10

16
go.sum
View File

@ -69,11 +69,16 @@ github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:ma
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/cpuguy83/go-md2man/v2 v2.0.1 h1:r/myEWzV9lfsM1tFLgDyu0atFtJ1fXn261LKYj/3DxU= github.com/cpuguy83/go-md2man/v2 v2.0.1 h1:r/myEWzV9lfsM1tFLgDyu0atFtJ1fXn261LKYj/3DxU=
github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91 h1:Izz0+t1Z5nI16/II7vuEo/nHjodOg0p7+OiDpjX5t1E=
github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
github.com/dop251/goja v0.0.0-20220110113543-261677941f3c h1:1XnAlcjYBdO7xsa2rhNB/BTztiu4cFKOxE+3brXVtG4=
github.com/dop251/goja v0.0.0-20220110113543-261677941f3c/go.mod h1:R9ET47fwRVRPZnOGvHxxhuZcbrMCuiqOz3Rlrh4KSnk=
github.com/dop251/goja_nodejs v0.0.0-20210225215109-d91c329300e7/go.mod h1:hn7BA7c8pLvoGndExHudxTDKZ84Pyvv+90pbBjbTz0Y=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
@ -91,6 +96,8 @@ github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeME
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-sourcemap/sourcemap v2.1.3+incompatible h1:W1iEw64niKVGogNgBN3ePyLFfuisuzeidWPMPWmECqU=
github.com/go-sourcemap/sourcemap v2.1.3+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
@ -196,8 +203,10 @@ github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
@ -227,7 +236,6 @@ github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FI
github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= github.com/pelletier/go-toml v1.9.3/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI= github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
@ -259,7 +267,6 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/tomnomnom/xtermcolor v0.0.0-20160428124646-b78803f00a7e h1:Ee+VZw13r9NTOMnwTPs6O5KZ0MJU54hsxu9FpZ4pQ10= github.com/tomnomnom/xtermcolor v0.0.0-20160428124646-b78803f00a7e h1:Ee+VZw13r9NTOMnwTPs6O5KZ0MJU54hsxu9FpZ4pQ10=
@ -459,6 +466,7 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@ -624,6 +632,7 @@ google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp0
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
@ -640,7 +649,6 @@ gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

View File

@ -14,7 +14,7 @@ import (
"git.kirsle.net/apps/doodle/pkg/enum" "git.kirsle.net/apps/doodle/pkg/enum"
"git.kirsle.net/apps/doodle/pkg/log" "git.kirsle.net/apps/doodle/pkg/log"
"git.kirsle.net/apps/doodle/pkg/modal" "git.kirsle.net/apps/doodle/pkg/modal"
"github.com/robertkrimen/otto" "github.com/dop251/goja"
) )
// Command is a parsed shell command. // Command is a parsed shell command.
@ -330,13 +330,13 @@ func (c Command) BoolProp(d *Doodle) error {
} }
// RunScript evaluates some JavaScript code safely. // RunScript evaluates some JavaScript code safely.
func (c Command) RunScript(d *Doodle, code interface{}) (otto.Value, error) { func (c Command) RunScript(d *Doodle, code string) (goja.Value, error) {
defer func() { defer func() {
if err := recover(); err != nil { if err := recover(); err != nil {
d.FlashError("Command.RunScript: Panic: %s", err) d.FlashError("Command.RunScript: Panic: %s", err)
} }
}() }()
out, err := d.shell.js.Run(code) out, err := d.shell.js.RunString(code)
return out, err return out, err
} }

View File

@ -79,7 +79,7 @@ func (u *EditorUI) SetupMenuBar(d *Doodle) *ui.MenuBar {
fileMenu.AddItemAccel("Open...", "Ctrl-O", u.Scene.MenuOpen) fileMenu.AddItemAccel("Open...", "Ctrl-O", u.Scene.MenuOpen)
fileMenu.AddSeparator() fileMenu.AddSeparator()
fileMenu.AddItem("Quit to menu", func() { fileMenu.AddItem("Exit to menu", func() {
u.Scene.ConfirmUnload(func() { u.Scene.ConfirmUnload(func() {
d.Goto(&MainScene{}) d.Goto(&MainScene{})
}) })

View File

@ -2,10 +2,11 @@ package scripting
import ( import (
"errors" "errors"
"fmt"
"sync" "sync"
"git.kirsle.net/apps/doodle/pkg/keybind" "git.kirsle.net/apps/doodle/pkg/keybind"
"github.com/robertkrimen/otto" "github.com/dop251/goja"
) )
// Event name constants. // Event name constants.
@ -26,19 +27,21 @@ var (
// Events API for Doodad scripts. // Events API for Doodad scripts.
type Events struct { type Events struct {
registry map[string][]otto.Value runtime *goja.Runtime
registry map[string][]goja.Value
lock sync.RWMutex lock sync.RWMutex
} }
// NewEvents initializes the Events API. // NewEvents initializes the Events API.
func NewEvents() *Events { func NewEvents(runtime *goja.Runtime) *Events {
return &Events{ return &Events{
registry: map[string][]otto.Value{}, runtime: runtime,
registry: map[string][]goja.Value{},
} }
} }
// OnCollide fires when another actor collides with yours. // OnCollide fires when another actor collides with yours.
func (e *Events) OnCollide(call otto.FunctionCall) otto.Value { func (e *Events) OnCollide(call goja.FunctionCall) goja.Value {
return e.register(CollideEvent, call.Argument(0)) return e.register(CollideEvent, call.Argument(0))
} }
@ -48,7 +51,7 @@ func (e *Events) RunCollide(v interface{}) error {
} }
// OnUse fires when another actor collides with yours. // OnUse fires when another actor collides with yours.
func (e *Events) OnUse(call otto.FunctionCall) otto.Value { func (e *Events) OnUse(call goja.FunctionCall) goja.Value {
return e.register(UseEvent, call.Argument(0)) return e.register(UseEvent, call.Argument(0))
} }
@ -58,7 +61,7 @@ func (e *Events) RunUse(v interface{}) error {
} }
// OnLeave fires when another actor stops colliding with yours. // OnLeave fires when another actor stops colliding with yours.
func (e *Events) OnLeave(call otto.FunctionCall) otto.Value { func (e *Events) OnLeave(call goja.FunctionCall) goja.Value {
return e.register(LeaveEvent, call.Argument(0)) return e.register(LeaveEvent, call.Argument(0))
} }
@ -68,30 +71,26 @@ func (e *Events) RunLeave(v interface{}) error {
} }
// OnKeypress fires when another actor collides with yours. // OnKeypress fires when another actor collides with yours.
func (e *Events) OnKeypress(call otto.FunctionCall) otto.Value { func (e *Events) OnKeypress(call goja.FunctionCall) goja.Value {
return e.register(KeypressEvent, call.Argument(0)) return e.register(KeypressEvent, call.Argument(0))
} }
// RunKeypress invokes the OnCollide handler function. // RunKeypress invokes the OnCollide handler function.
func (e *Events) RunKeypress(ev keybind.State) error { func (e *Events) RunKeypress(ev keybind.State) error {
return e.run(KeypressEvent, ev) return e.run(KeypressEvent, e.runtime.ToValue(ev))
} }
// register a named event. // register a named event.
func (e *Events) register(name string, callback otto.Value) otto.Value { func (e *Events) register(name string, callback goja.Value) goja.Value {
if !callback.IsFunction() {
return otto.Value{} // TODO
}
e.lock.Lock() e.lock.Lock()
defer e.lock.Unlock() defer e.lock.Unlock()
if _, ok := e.registry[name]; !ok { if _, ok := e.registry[name]; !ok {
e.registry[name] = []otto.Value{} e.registry[name] = []goja.Value{}
} }
e.registry[name] = append(e.registry[name], callback) e.registry[name] = append(e.registry[name], callback)
return otto.Value{} return goja.Undefined()
} }
// Run an event handler. Returns an error only if there was a JavaScript error // Run an event handler. Returns an error only if there was a JavaScript error
@ -104,20 +103,28 @@ func (e *Events) run(name string, args ...interface{}) error {
return nil return nil
} }
var params = make([]goja.Value, len(args))
for i, v := range args {
params[i] = e.runtime.ToValue(v)
}
for _, callback := range e.registry[name] { for _, callback := range e.registry[name] {
value, err := callback.Call(otto.Value{}, args...) function, ok := goja.AssertFunction(callback)
if !ok {
return fmt.Errorf("failed to callback %s: %s", name, callback)
}
value, err := function(goja.Undefined(), params...)
if err != nil { if err != nil {
return err return err
} }
// If the event handler returned a boolean false, stop all other // If the event handler returned a boolean false, stop all other
// callbacks and return the boolean. // callbacks and return the boolean.
if value.IsBoolean() { if b, ok := value.Export().(bool); ok && !b {
if b, err := value.ToBoolean(); err == nil && b == false {
return ErrReturnFalse return ErrReturnFalse
} }
} }
}
return nil return nil
} }

View File

@ -2,7 +2,7 @@ package scripting
import ( import (
"git.kirsle.net/apps/doodle/pkg/log" "git.kirsle.net/apps/doodle/pkg/log"
"github.com/robertkrimen/otto" "github.com/dop251/goja"
) )
// Message holds data being published from one script VM with information sent // Message holds data being published from one script VM with information sent
@ -10,7 +10,7 @@ import (
type Message struct { type Message struct {
Name string Name string
SenderID string SenderID string
Args []interface{} Args []goja.Value
} }
/* /*
@ -29,7 +29,10 @@ func RegisterPublishHooks(s *Supervisor, vm *VM) {
if _, ok := vm.subscribe[msg.Name]; ok { if _, ok := vm.subscribe[msg.Name]; ok {
for _, callback := range vm.subscribe[msg.Name] { for _, callback := range vm.subscribe[msg.Name] {
log.Debug("PubSub: %s receives from %s: %s", vm.Name, msg.SenderID, msg.Name) log.Debug("PubSub: %s receives from %s: %s", vm.Name, msg.SenderID, msg.Name)
callback.Call(otto.Value{}, msg.Args...) if function, ok := goja.AssertFunction(callback); ok {
result, err := function(goja.Undefined(), msg.Args...)
log.Debug("Result: %s, %s", result, err)
}
} }
} }
@ -39,22 +42,22 @@ func RegisterPublishHooks(s *Supervisor, vm *VM) {
// Register the Message.Subscribe and Message.Publish functions. // Register the Message.Subscribe and Message.Publish functions.
vm.vm.Set("Message", map[string]interface{}{ vm.vm.Set("Message", map[string]interface{}{
"Subscribe": func(name string, callback otto.Value) { "Subscribe": func(name string, callback goja.Value) {
vm.muSubscribe.Lock() vm.muSubscribe.Lock()
defer vm.muSubscribe.Unlock() defer vm.muSubscribe.Unlock()
if !callback.IsFunction() { if _, ok := goja.AssertFunction(callback); !ok {
log.Error("SUBSCRIBE(%s): callback is not a function", name) log.Error("SUBSCRIBE(%s): callback is not a function", name)
return return
} }
if _, ok := vm.subscribe[name]; !ok { if _, ok := vm.subscribe[name]; !ok {
vm.subscribe[name] = []otto.Value{} vm.subscribe[name] = []goja.Value{}
} }
vm.subscribe[name] = append(vm.subscribe[name], callback) vm.subscribe[name] = append(vm.subscribe[name], callback)
}, },
"Publish": func(name string, v ...interface{}) { "Publish": func(name string, v ...goja.Value) {
for _, channel := range vm.Outbound { for _, channel := range vm.Outbound {
channel <- Message{ channel <- Message{
Name: name, Name: name,
@ -64,7 +67,7 @@ func RegisterPublishHooks(s *Supervisor, vm *VM) {
} }
}, },
"Broadcast": func(name string, v ...interface{}) { "Broadcast": func(name string, v ...goja.Value) {
// Send the message to all actor VMs. // Send the message to all actor VMs.
for _, toVM := range s.scripts { for _, toVM := range s.scripts {
if vm.Name == toVM.Name { if vm.Name == toVM.Name {

View File

@ -3,13 +3,13 @@ package scripting
import ( import (
"time" "time"
"github.com/robertkrimen/otto" "github.com/dop251/goja"
) )
// Timer keeps track of delayed function calls for the scripting engine. // Timer keeps track of delayed function calls for the scripting engine.
type Timer struct { type Timer struct {
id int id int
callback otto.Value callback goja.Value
interval time.Duration // milliseconds delay for timeout interval time.Duration // milliseconds delay for timeout
next time.Time // scheduled time for next invocation next time.Time // scheduled time for next invocation
repeat bool // for setInterval repeat bool // for setInterval
@ -27,7 +27,7 @@ with 1000 being 'one second.'
Returns the ID number of the timer in case you want to clear it. The underlying Returns the ID number of the timer in case you want to clear it. The underlying
Timer type is NOT exposed to JavaScript. Timer type is NOT exposed to JavaScript.
*/ */
func (vm *VM) SetTimeout(callback otto.Value, interval int) int { func (vm *VM) SetTimeout(callback goja.Value, interval int) int {
return vm.AddTimer(callback, interval, false) return vm.AddTimer(callback, interval, false)
} }
@ -37,14 +37,14 @@ SetInterval registers a callback function to be run repeatedly.
Returns the ID number of the timer in case you want to clear it. The underlying Returns the ID number of the timer in case you want to clear it. The underlying
Timer type is NOT exposed to JavaScript. Timer type is NOT exposed to JavaScript.
*/ */
func (vm *VM) SetInterval(callback otto.Value, interval int) int { func (vm *VM) SetInterval(callback goja.Value, interval int) int {
return vm.AddTimer(callback, interval, true) return vm.AddTimer(callback, interval, true)
} }
/* /*
AddTimer loads timeouts and intervals into the VM's memory and returns the ID. AddTimer loads timeouts and intervals into the VM's memory and returns the ID.
*/ */
func (vm *VM) AddTimer(callback otto.Value, interval int, repeat bool) int { func (vm *VM) AddTimer(callback goja.Value, interval int, repeat bool) int {
// Get the next timer ID. The first timer has ID 1. // Get the next timer ID. The first timer has ID 1.
vm.timerLastID++ vm.timerLastID++
id := vm.timerLastID id := vm.timerLastID
@ -72,7 +72,10 @@ func (vm *VM) TickTimer(now time.Time) {
for id, timer := range vm.timers { for id, timer := range vm.timers {
if now.After(timer.next) { if now.After(timer.next) {
timer.callback.Call(otto.Value{}) if function, ok := goja.AssertFunction(timer.callback); ok {
function(goja.Undefined())
}
if timer.repeat { if timer.repeat {
timer.Schedule() timer.Schedule()
} else { } else {

View File

@ -1,11 +1,12 @@
package scripting package scripting
import ( import (
"errors"
"fmt" "fmt"
"sync" "sync"
"git.kirsle.net/apps/doodle/pkg/log" "git.kirsle.net/apps/doodle/pkg/log"
"github.com/robertkrimen/otto" "github.com/dop251/goja"
) )
// VM manages a single isolated JavaScript VM. // VM manages a single isolated JavaScript VM.
@ -24,10 +25,10 @@ type VM struct {
// messages. // messages.
Inbound chan Message Inbound chan Message
Outbound []chan Message Outbound []chan Message
subscribe map[string][]otto.Value // Subscribed message handlers by name. subscribe map[string][]goja.Value // Subscribed message handlers by name.
muSubscribe sync.RWMutex muSubscribe sync.RWMutex
vm *otto.Otto vm *goja.Runtime
// setTimeout and setInterval variables. // setTimeout and setInterval variables.
timerLastID int // becomes 1 when first timer is set timerLastID int // becomes 1 when first timer is set
@ -38,21 +39,21 @@ type VM struct {
func NewVM(name string) *VM { func NewVM(name string) *VM {
vm := &VM{ vm := &VM{
Name: name, Name: name,
Events: NewEvents(), vm: goja.New(),
vm: otto.New(),
timers: map[int]*Timer{}, timers: map[int]*Timer{},
// Pub/sub structs. // Pub/sub structs.
Inbound: make(chan Message), Inbound: make(chan Message),
Outbound: []chan Message{}, Outbound: []chan Message{},
subscribe: map[string][]otto.Value{}, subscribe: map[string][]goja.Value{},
} }
vm.Events = NewEvents(vm.vm)
return vm return vm
} }
// Run code in the VM. // Run code in the VM.
func (vm *VM) Run(src interface{}) (otto.Value, error) { func (vm *VM) Run(src string) (goja.Value, error) {
v, err := vm.vm.Run(src) v, err := vm.vm.RunString(src)
return v, err return v, err
} }
@ -78,13 +79,9 @@ func (vm *VM) RegisterLevelHooks() error {
// Main calls the main function of the script. // Main calls the main function of the script.
func (vm *VM) Main() error { func (vm *VM) Main() error {
function, err := vm.vm.Get("main") function, ok := goja.AssertFunction(vm.vm.Get("main"))
if err != nil { if !ok {
return err return errors.New("didn't find function main()")
}
if !function.IsFunction() {
return nil
} }
// Catch panics. // Catch panics.
@ -94,6 +91,6 @@ func (vm *VM) Main() error {
} }
}() }()
_, err = function.Call(otto.Value{}) _, err := function(goja.Undefined())
return err return err
} }

View File

@ -14,7 +14,7 @@ import (
"git.kirsle.net/go/render" "git.kirsle.net/go/render"
"git.kirsle.net/go/render/event" "git.kirsle.net/go/render/event"
"git.kirsle.net/go/ui" "git.kirsle.net/go/ui"
"github.com/robertkrimen/otto" "github.com/dop251/goja"
) )
// Flash a message to the user. // Flash a message to the user.
@ -59,7 +59,7 @@ type Shell struct {
historyIndex int historyIndex int
// JavaScript shell interpreter. // JavaScript shell interpreter.
js *otto.Otto js *goja.Runtime
} }
// Flash holds a message to flash on screen. // Flash holds a message to flash on screen.
@ -79,7 +79,7 @@ func NewShell(d *Doodle) Shell {
Prompt: ">", Prompt: ">",
cursor: '_', cursor: '_',
cursorRate: balance.ShellCursorBlinkRate, cursorRate: balance.ShellCursorBlinkRate,
js: otto.New(), js: goja.New(),
} }
// Make the Doodle instance available to the shell. // Make the Doodle instance available to the shell.

View File

@ -12,8 +12,8 @@ import (
"git.kirsle.net/apps/doodle/pkg/log" "git.kirsle.net/apps/doodle/pkg/log"
"git.kirsle.net/apps/doodle/pkg/physics" "git.kirsle.net/apps/doodle/pkg/physics"
"git.kirsle.net/go/render" "git.kirsle.net/go/render"
"github.com/dop251/goja"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/robertkrimen/otto"
) )
// Actor is an object that marries together the three things that make a // Actor is an object that marries together the three things that make a
@ -52,7 +52,7 @@ type Actor struct {
// Animation variables. // Animation variables.
animations map[string]*Animation animations map[string]*Animation
activeAnimation *Animation activeAnimation *Animation
animationCallback otto.Value animationCallback goja.Value
// Mutex. // Mutex.
muInventory sync.RWMutex muInventory sync.RWMutex

View File

@ -7,7 +7,7 @@ import (
"time" "time"
"git.kirsle.net/apps/doodle/pkg/log" "git.kirsle.net/apps/doodle/pkg/log"
"github.com/robertkrimen/otto" "github.com/dop251/goja"
) )
// Animation holds a named animation for a doodad script. // Animation holds a named animation for a doodad script.
@ -103,7 +103,7 @@ func (a *Actor) AddAnimation(name string, interval int64, layers []interface{})
// PlayAnimation starts an animation and then calls a JavaScript function when // PlayAnimation starts an animation and then calls a JavaScript function when
// the last frame has played out. Set a null function to ignore the callback. // the last frame has played out. Set a null function to ignore the callback.
func (a *Actor) PlayAnimation(name string, callback otto.Value) error { func (a *Actor) PlayAnimation(name string, callback goja.Value) error {
anim, ok := a.animations[name] anim, ok := a.animations[name]
if !ok { if !ok {
return fmt.Errorf("animation named '%s' not found", name) return fmt.Errorf("animation named '%s' not found", name)
@ -132,5 +132,5 @@ func (a *Actor) StopAnimation() {
} }
a.activeAnimation = nil a.activeAnimation = nil
a.animationCallback = otto.NullValue() a.animationCallback = goja.Null()
} }

View File

@ -10,7 +10,7 @@ import (
"git.kirsle.net/apps/doodle/pkg/physics" "git.kirsle.net/apps/doodle/pkg/physics"
"git.kirsle.net/apps/doodle/pkg/scripting" "git.kirsle.net/apps/doodle/pkg/scripting"
"git.kirsle.net/go/render" "git.kirsle.net/go/render"
"github.com/robertkrimen/otto" "github.com/dop251/goja"
) )
// loopActorCollision is the Loop function that checks if pairs of // loopActorCollision is the Loop function that checks if pairs of
@ -56,8 +56,8 @@ func (w *Canvas) loopActorCollision() error {
a.StopAnimation() a.StopAnimation()
// Call the callback function. // Call the callback function.
if callback.IsFunction() { if function, ok := goja.AssertFunction(callback); ok {
callback.Call(otto.NullValue()) function(goja.Undefined())
} }
} }