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!
This commit is contained in:
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
### github.com/robertkrimen/otto
### github.com/dop251/goja
```
Copyright (c) 2016 Dmitry Panov
Copyright (c) 2012 Robert Krimen
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
documentation files (the "Software"), to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 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
copies or substantial portions of the Software.
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
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.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 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

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

View File

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

View File

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

View File

@ -1,10 +1,10 @@
const playerSpeed = 12;
let Vx = Vy = 0,
animating = false,
animStart = animEnd = 0;
function main() {
var playerSpeed = 12;
var Vx = Vy = 0;
var animating = false;
var animStart = animEnd = 0;
Self.SetMobile(true);
Self.SetInventory(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"]);
// 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;
Vy = 0;
var curVelocity = Self.GetVelocity();
let curVelocity = Self.GetVelocity();
if ((lastVelocity.X < 0 && curVelocity.X > 0) ||
(lastVelocity.X > 0 && curVelocity.X < 0)) {
Self.StopAnimation();
@ -40,7 +40,5 @@ function main() {
Self.StopAnimation();
animating = false;
}
// Self.SetVelocity(Point(Vx, Vy));
})
}

View File

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

View File

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

View File

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

View File

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

View File

@ -1,6 +1,8 @@
var animating = false;
var opened = false;
var powerState = false;
// Electric Door
let animating = false;
let opened = false;
let powerState = false;
// Function to handle the door opening or closing.
function setPoweredState(powered) {
@ -13,14 +15,14 @@ function setPoweredState(powered) {
animating = true;
Sound.Play("electric-door.wav")
Self.PlayAnimation("open", function () {
Self.PlayAnimation("open", () => {
opened = true;
animating = false;
});
} else {
animating = true;
Sound.Play("electric-door.wav")
Self.PlayAnimation("close", function () {
Self.PlayAnimation("close", () => {
opened = false;
animating = false;
})
@ -31,7 +33,6 @@ function main() {
Self.AddAnimation("open", 100, [0, 1, 2, 3]);
Self.AddAnimation("close", 100, [3, 2, 1, 0]);
Self.SetHitbox(0, 0, 34, 76);
// 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
// power sources like Buttons will work as normal, as they emit only a power
// signal.
var ignoreNextPower = false;
Message.Subscribe("switch:toggle", function (powered) {
let ignoreNextPower = false;
Message.Subscribe("switch:toggle", (powered) => {
ignoreNextPower = true;
setPoweredState(!powerState);
})
Message.Subscribe("power", function (powered) {
Message.Subscribe("power", (powered) => {
if (ignoreNextPower) {
ignoreNextPower = false;
return;
@ -54,7 +55,7 @@ function main() {
setPoweredState(powered);
});
Events.OnCollide(function (e) {
Events.OnCollide((e) => {
if (e.InHitbox) {
if (!opened) {
return false;

View File

@ -1,8 +1,10 @@
function main() {
var color = Self.GetTag("color");
var quantity = color === "small" ? 1 : 0;
// Colored Keys and Small Key
Events.OnCollide(function (e) {
const color = Self.GetTag("color"),
quantity = color === "small" ? 1 : 0;
function main() {
Events.OnCollide((e) => {
if (e.Settled) {
if (e.Actor.HasInventory()) {
// 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);
// Monitor our Y position to tell if we've been falling.
var lastPoint = Self.Position();
setInterval(function () {
var nowAt = Self.Position();
let lastPoint = Self.Position();
setInterval(() => {
let nowAt = Self.Position();
if (nowAt.Y > lastPoint.Y) {
falling = true;
} else {
@ -19,7 +19,7 @@ function main() {
lastPoint = nowAt;
}, 100);
Events.OnCollide(function (e) {
Events.OnCollide((e) => {
if (!e.Settled) {
return;
}
@ -43,8 +43,8 @@ function main() {
});
// When we receive power, we reset to our original position.
var origPoint = Self.Position();
Message.Subscribe("power", function (powered) {
let origPoint = Self.Position();
Message.Subscribe("power", (powered) => {
Self.MoveTo(origPoint);
Self.SetVelocity(Vector(0, 0));
});

View File

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

View File

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

View File

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

View File

@ -3,9 +3,9 @@ function main() {
Self.SetHitbox(0, 0, 42, 42);
// 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;
// Layer 0: ON
@ -13,7 +13,7 @@ function main() {
Self.ShowLayer(state ? 0 : 1);
});
Events.OnCollide(function(e) {
Events.OnCollide((e) => {
if (e.Actor.IsMobile() && e.InHitbox) {
if (state) {
return false;

View File

@ -3,9 +3,9 @@ function main() {
Self.SetHitbox(0, 0, 42, 42);
// 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;
// Layer 0: OFF
@ -13,7 +13,7 @@ function main() {
Self.ShowLayer(state ? 1 : 0);
});
Events.OnCollide(function(e) {
Events.OnCollide((e) => {
if (e.Actor.IsMobile() && e.InHitbox) {
if (state) {
return false;

View File

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

View File

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

View File

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

View File

@ -2,7 +2,7 @@
function main() {
Self.Hide();
Events.OnCollide(function (e) {
Events.OnCollide((e) => {
if (!e.Settled) {
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("power", true);
});

View File

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

View File

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

View File

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

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

View File

@ -1,16 +1,17 @@
// 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() {
// 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.
var animSpeed = 100;
var spriteDefault, spriteDisabled; // the latter for state doors.
let animSpeed = 100;
let spriteDefault, spriteDisabled; // the latter for state doors.
if (color === 'blue') {
Self.AddAnimation("open", animSpeed, ["blue-2", "blue-3", "blue-4"]);
Self.AddAnimation("close", animSpeed, ["blue-4", "blue-3", "blue-2", "blue-1"]);
@ -33,17 +34,17 @@ function main() {
}
// Find our linked Warp Door.
var links = Self.GetLinks()
var linkedDoor = null;
for (var i = 0; i < links.length; i++) {
if (links[i].Title.indexOf("Warp Door") > -1) {
linkedDoor = links[i];
let linkedDoor = null;
for (let link of Self.GetLinks()) {
if (link.Title.indexOf("Warp Door") > -1) {
linkedDoor = link;
break;
}
}
// Subscribe to the global state-change if we are a state door.
if (isStateDoor) {
Message.Subscribe("broadcast:state-change", function(newState) {
Message.Subscribe("broadcast:state-change", (newState) => {
state = color === 'blue' ? !newState : newState;
// Activate or deactivate the door.
@ -52,12 +53,11 @@ function main() {
}
// For player groundedness work-around
var playerLastY = []; // last sampling of Y values
var lastUsed = time.Now();
let playerLastY = []; // last sampling of Y values
// The player Uses the door.
var flashedCooldown = false; // "Locked Door" flashed message.
Events.OnUse(function(e) {
let flashedCooldown = false; // "Locked Door" flashed message.
Events.OnUse((e) => {
if (animating) {
return;
}
@ -86,7 +86,7 @@ function main() {
// 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
// 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
// is constant the last handful of frames, treat them as if they're
@ -100,8 +100,8 @@ function main() {
playerLastY.pop();
// Hasn't moved?
var isGrounded = true;
for (var i = 0; i < playerLastY.length; i++) {
let isGrounded = true;
for (let i = 0; i < playerLastY.length; i++) {
if (yValue !== playerLastY[i]) {
isGrounded = false;
break;
@ -120,9 +120,9 @@ function main() {
// Play the open and close animation.
animating = true;
Self.PlayAnimation("open", function() {
Self.PlayAnimation("open", () => {
e.Actor.Hide()
Self.PlayAnimation("close", function() {
Self.PlayAnimation("close", () => {
Self.ShowLayerNamed(isStateDoor && !state ? spriteDisabled : spriteDefault);
animating = false;
@ -139,12 +139,12 @@ function main() {
});
// Respond to incoming warp events.
Message.Subscribe("warp-door:incoming", function(player) {
Message.Subscribe("warp-door:incoming", (player) => {
animating = true;
player.Unfreeze();
Self.PlayAnimation("open", function() {
Self.PlayAnimation("open", () => {
player.Show();
Self.PlayAnimation("close", function() {
Self.PlayAnimation("close", () => {
animating = false;
// 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/cpuguy83/go-md2man/v2 v2.0.1 // indirect
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/gen2brain/dlgs v0.0.0-20211108104213-bade24837f0b
github.com/google/uuid v1.3.0
github.com/gopherjs/gopherjs v0.0.0-20220104163920-15ed2e8cf2bd // indirect
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/urfave/cli/v2 v2.3.0
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.1 h1:r/myEWzV9lfsM1tFLgDyu0atFtJ1fXn261LKYj/3DxU=
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.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
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/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.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
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/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-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/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
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/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.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
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.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
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-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/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
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/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
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.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.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
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/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.4/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/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=
@ -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=
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-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
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/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/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-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
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-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/log"
"git.kirsle.net/apps/doodle/pkg/modal"
"github.com/robertkrimen/otto"
"github.com/dop251/goja"
)
// Command is a parsed shell command.
@ -330,13 +330,13 @@ func (c Command) BoolProp(d *Doodle) error {
}
// 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() {
if err := recover(); err != nil {
d.FlashError("Command.RunScript: Panic: %s", err)
}
}()
out, err := d.shell.js.Run(code)
out, err := d.shell.js.RunString(code)
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.AddSeparator()
fileMenu.AddItem("Quit to menu", func() {
fileMenu.AddItem("Exit to menu", func() {
u.Scene.ConfirmUnload(func() {
d.Goto(&MainScene{})
})

View File

@ -2,10 +2,11 @@ package scripting
import (
"errors"
"fmt"
"sync"
"git.kirsle.net/apps/doodle/pkg/keybind"
"github.com/robertkrimen/otto"
"github.com/dop251/goja"
)
// Event name constants.
@ -26,19 +27,21 @@ var (
// Events API for Doodad scripts.
type Events struct {
registry map[string][]otto.Value
runtime *goja.Runtime
registry map[string][]goja.Value
lock sync.RWMutex
}
// NewEvents initializes the Events API.
func NewEvents() *Events {
func NewEvents(runtime *goja.Runtime) *Events {
return &Events{
registry: map[string][]otto.Value{},
runtime: runtime,
registry: map[string][]goja.Value{},
}
}
// 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))
}
@ -48,7 +51,7 @@ func (e *Events) RunCollide(v interface{}) error {
}
// 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))
}
@ -58,7 +61,7 @@ func (e *Events) RunUse(v interface{}) error {
}
// 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))
}
@ -68,30 +71,26 @@ func (e *Events) RunLeave(v interface{}) error {
}
// 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))
}
// RunKeypress invokes the OnCollide handler function.
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.
func (e *Events) register(name string, callback otto.Value) otto.Value {
if !callback.IsFunction() {
return otto.Value{} // TODO
}
func (e *Events) register(name string, callback goja.Value) goja.Value {
e.lock.Lock()
defer e.lock.Unlock()
if _, ok := e.registry[name]; !ok {
e.registry[name] = []otto.Value{}
e.registry[name] = []goja.Value{}
}
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
@ -104,18 +103,26 @@ func (e *Events) run(name string, args ...interface{}) error {
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] {
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 {
return err
}
// If the event handler returned a boolean false, stop all other
// callbacks and return the boolean.
if value.IsBoolean() {
if b, err := value.ToBoolean(); err == nil && b == false {
return ErrReturnFalse
}
if b, ok := value.Export().(bool); ok && !b {
return ErrReturnFalse
}
}

View File

@ -2,7 +2,7 @@ package scripting
import (
"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
@ -10,7 +10,7 @@ import (
type Message struct {
Name 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 {
for _, callback := range vm.subscribe[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.
vm.vm.Set("Message", map[string]interface{}{
"Subscribe": func(name string, callback otto.Value) {
"Subscribe": func(name string, callback goja.Value) {
vm.muSubscribe.Lock()
defer vm.muSubscribe.Unlock()
if !callback.IsFunction() {
if _, ok := goja.AssertFunction(callback); !ok {
log.Error("SUBSCRIBE(%s): callback is not a function", name)
return
}
if _, ok := vm.subscribe[name]; !ok {
vm.subscribe[name] = []otto.Value{}
vm.subscribe[name] = []goja.Value{}
}
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 {
channel <- Message{
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.
for _, toVM := range s.scripts {
if vm.Name == toVM.Name {

View File

@ -3,13 +3,13 @@ package scripting
import (
"time"
"github.com/robertkrimen/otto"
"github.com/dop251/goja"
)
// Timer keeps track of delayed function calls for the scripting engine.
type Timer struct {
id int
callback otto.Value
callback goja.Value
interval time.Duration // milliseconds delay for timeout
next time.Time // scheduled time for next invocation
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
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)
}
@ -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
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)
}
/*
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.
vm.timerLastID++
id := vm.timerLastID
@ -72,7 +72,10 @@ func (vm *VM) TickTimer(now time.Time) {
for id, timer := range vm.timers {
if now.After(timer.next) {
timer.callback.Call(otto.Value{})
if function, ok := goja.AssertFunction(timer.callback); ok {
function(goja.Undefined())
}
if timer.repeat {
timer.Schedule()
} else {

View File

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

View File

@ -14,7 +14,7 @@ import (
"git.kirsle.net/go/render"
"git.kirsle.net/go/render/event"
"git.kirsle.net/go/ui"
"github.com/robertkrimen/otto"
"github.com/dop251/goja"
)
// Flash a message to the user.
@ -59,7 +59,7 @@ type Shell struct {
historyIndex int
// JavaScript shell interpreter.
js *otto.Otto
js *goja.Runtime
}
// Flash holds a message to flash on screen.
@ -79,7 +79,7 @@ func NewShell(d *Doodle) Shell {
Prompt: ">",
cursor: '_',
cursorRate: balance.ShellCursorBlinkRate,
js: otto.New(),
js: goja.New(),
}
// 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/physics"
"git.kirsle.net/go/render"
"github.com/dop251/goja"
"github.com/google/uuid"
"github.com/robertkrimen/otto"
)
// Actor is an object that marries together the three things that make a
@ -52,7 +52,7 @@ type Actor struct {
// Animation variables.
animations map[string]*Animation
activeAnimation *Animation
animationCallback otto.Value
animationCallback goja.Value
// Mutex.
muInventory sync.RWMutex

View File

@ -7,7 +7,7 @@ import (
"time"
"git.kirsle.net/apps/doodle/pkg/log"
"github.com/robertkrimen/otto"
"github.com/dop251/goja"
)
// 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
// 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]
if !ok {
return fmt.Errorf("animation named '%s' not found", name)
@ -132,5 +132,5 @@ func (a *Actor) StopAnimation() {
}
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/scripting"
"git.kirsle.net/go/render"
"github.com/robertkrimen/otto"
"github.com/dop251/goja"
)
// loopActorCollision is the Loop function that checks if pairs of
@ -56,8 +56,8 @@ func (w *Canvas) loopActorCollision() error {
a.StopAnimation()
// Call the callback function.
if callback.IsFunction() {
callback.Call(otto.NullValue())
if function, ok := goja.AssertFunction(callback); ok {
function(goja.Undefined())
}
}