Split doodads into new repository
|
@ -1,27 +0,0 @@
|
||||||
SHELL = /bin/bash
|
|
||||||
|
|
||||||
ALL: build
|
|
||||||
|
|
||||||
.PHONY: build
|
|
||||||
build:
|
|
||||||
doodad convert -t "Blue Azulian" blu-front.png blu-back.png \
|
|
||||||
blu-wr{1,2,3,4}.png blu-wl{1,2,3,4}.png azu-blu.doodad
|
|
||||||
doodad edit-doodad --tag "color=blue" azu-blu.doodad
|
|
||||||
doodad install-script azulian.js azu-blu.doodad
|
|
||||||
|
|
||||||
doodad convert -t "Red Azulian" red-front.png red-back.png \
|
|
||||||
red-wr{1,2,3,4}.png red-wl{1,2,3,4}.png azu-red.doodad
|
|
||||||
doodad edit-doodad --tag "color=red" azu-red.doodad
|
|
||||||
doodad install-script azulian.js azu-red.doodad
|
|
||||||
|
|
||||||
doodad convert -t "White Azulian" white-front.png white-back.png \
|
|
||||||
white-wr{1,2,3,4}.png white-wl{1,2,3,4}.png azu-white.doodad
|
|
||||||
doodad edit-doodad --tag "color=white" azu-white.doodad
|
|
||||||
doodad install-script azulian.js azu-white.doodad
|
|
||||||
|
|
||||||
# Tag the category for these doodads
|
|
||||||
for i in *.doodad; do\
|
|
||||||
doodad edit-doodad --tag "category=creatures" $${i};\
|
|
||||||
done
|
|
||||||
|
|
||||||
cp *.doodad ../../../assets/doodads/
|
|
|
@ -1,178 +0,0 @@
|
||||||
// Azulian (Red and Blue)
|
|
||||||
|
|
||||||
const color = Self.GetTag("color");
|
|
||||||
var playerSpeed = color === 'blue' ? 2 : 4,
|
|
||||||
swimSpeed = playerSpeed * 0.4,
|
|
||||||
aggroX = 250, // X/Y distance sensitivity from player
|
|
||||||
aggroY = color === 'blue' ? 100 : 200,
|
|
||||||
jumpSpeed = color === 'blue' ? 14 : 18,
|
|
||||||
swimJumpSpeed = jumpSpeed * 0.4,
|
|
||||||
animating = false,
|
|
||||||
direction = "right",
|
|
||||||
lastDirection = "right";
|
|
||||||
|
|
||||||
// white Azulian is faster yet than the red
|
|
||||||
if (color === 'white') {
|
|
||||||
aggroX = 1000;
|
|
||||||
aggroY = 400;
|
|
||||||
playerSpeed = 8;
|
|
||||||
swimSpeed = playerSpeed * 0.4;
|
|
||||||
jumpSpeed = 20;
|
|
||||||
swimJumpSpeed = jumpSpeed * 0.4;
|
|
||||||
}
|
|
||||||
|
|
||||||
function setupAnimations(color) {
|
|
||||||
let left = color === 'blue' ? 'blu-wl' : color + '-wl',
|
|
||||||
right = color === 'blue' ? 'blu-wr' : color + '-wr',
|
|
||||||
leftFrames = [left + '1', left + '2', left + '3', left + '4'],
|
|
||||||
rightFrames = [right + '1', right + '2', right + '3', right + '4'];
|
|
||||||
|
|
||||||
Self.AddAnimation("walk-left", 100, leftFrames);
|
|
||||||
Self.AddAnimation("walk-right", 100, rightFrames);
|
|
||||||
}
|
|
||||||
|
|
||||||
function main() {
|
|
||||||
playerSpeed = color === 'blue' ? 2 : 4;
|
|
||||||
|
|
||||||
let swimJumpCooldownTick = 0, // minimum Game Tick before we can jump while swimming
|
|
||||||
swimJumpCooldown = 10; // CONFIG: delta of ticks between jumps while swimming
|
|
||||||
|
|
||||||
Self.SetMobile(true);
|
|
||||||
Self.SetGravity(true);
|
|
||||||
Self.SetInventory(true);
|
|
||||||
Self.SetHitbox(0, 0, 24, 32);
|
|
||||||
setupAnimations(color);
|
|
||||||
|
|
||||||
if (Self.IsPlayer()) {
|
|
||||||
return playerControls();
|
|
||||||
}
|
|
||||||
|
|
||||||
// A.I. pattern: walks back and forth, turning around
|
|
||||||
// when it meets resistance.
|
|
||||||
|
|
||||||
// Sample our X position every few frames and detect if we've hit a solid wall.
|
|
||||||
let sampleTick = 0;
|
|
||||||
let sampleRate = 5;
|
|
||||||
let lastSampledX = 0;
|
|
||||||
|
|
||||||
// Get the player on touch.
|
|
||||||
Events.OnCollide((e) => {
|
|
||||||
// If we're diving and we hit the player, game over!
|
|
||||||
// Azulians are friendly to Thieves though!
|
|
||||||
if (e.Settled && isPlayerFood(e.Actor)) {
|
|
||||||
FailLevel("Watch out for the Azulians!");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
setInterval(() => {
|
|
||||||
// If the player is nearby, walk towards them. Otherwise, default pattern
|
|
||||||
// is to walk back and forth.
|
|
||||||
let player = Actors.FindPlayer(),
|
|
||||||
followPlayer = false,
|
|
||||||
jump = false;
|
|
||||||
|
|
||||||
// Don't follow boring players.
|
|
||||||
if (player !== null && isPlayerFood(player)) {
|
|
||||||
let playerPt = player.Position(),
|
|
||||||
myPt = Self.Position();
|
|
||||||
|
|
||||||
// If the player is within aggro range, move towards.
|
|
||||||
if ((Math.abs(playerPt.X - myPt.X) < aggroX && Math.abs(playerPt.Y - myPt.Y) < aggroY)
|
|
||||||
|| (Level.Difficulty > 0)) {
|
|
||||||
direction = playerPt.X < myPt.X ? "left" : "right";
|
|
||||||
followPlayer = true;
|
|
||||||
|
|
||||||
if (playerPt.Y + player.Size().H < myPt.Y + Self.Size().H) {
|
|
||||||
jump = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Default AI: sample position so we turn around on obstacles.
|
|
||||||
if (!followPlayer) {
|
|
||||||
if (sampleTick % sampleRate === 0) {
|
|
||||||
let curX = Self.Position().X;
|
|
||||||
let delta = Math.abs(curX - lastSampledX);
|
|
||||||
if (delta < 5) {
|
|
||||||
direction = direction === "right" ? "left" : "right";
|
|
||||||
}
|
|
||||||
lastSampledX = curX;
|
|
||||||
}
|
|
||||||
sampleTick++;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle being underwater.
|
|
||||||
let canJump = Self.Grounded();
|
|
||||||
if (Self.IsWet()) {
|
|
||||||
let tick = GetTick();
|
|
||||||
if (tick > swimJumpCooldownTick) {
|
|
||||||
canJump = true;
|
|
||||||
swimJumpCooldownTick = tick + swimJumpCooldown;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// How speedy would our movement and jump be?
|
|
||||||
let xspeed = playerSpeed, yspeed = jumpSpeed;
|
|
||||||
if (Self.IsWet()) {
|
|
||||||
xspeed = swimSpeed;
|
|
||||||
yspeed = swimJumpSpeed;
|
|
||||||
}
|
|
||||||
|
|
||||||
let Vx = parseFloat(xspeed * (direction === "left" ? -1 : 1)),
|
|
||||||
Vy = jump && canJump ? parseFloat(-yspeed) : Self.GetVelocity().Y;
|
|
||||||
Self.SetVelocity(Vector(Vx, Vy));
|
|
||||||
|
|
||||||
// If we changed directions, stop animating now so we can
|
|
||||||
// turn around quickly without moonwalking.
|
|
||||||
if (direction !== lastDirection) {
|
|
||||||
Self.StopAnimation();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!Self.IsAnimating()) {
|
|
||||||
Self.PlayAnimation("walk-" + direction, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
lastDirection = direction;
|
|
||||||
}, 10);
|
|
||||||
}
|
|
||||||
|
|
||||||
function playerControls() {
|
|
||||||
// Note: player speed is controlled by the engine.
|
|
||||||
Events.OnKeypress((ev) => {
|
|
||||||
if (ev.Right) {
|
|
||||||
if (!Self.IsAnimating()) {
|
|
||||||
Self.PlayAnimation("walk-right", null);
|
|
||||||
}
|
|
||||||
} else if (ev.Left) {
|
|
||||||
if (!Self.IsAnimating()) {
|
|
||||||
Self.PlayAnimation("walk-left", null);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Self.StopAnimation();
|
|
||||||
animating = false;
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Logic to decide if the player is interesting to the Azulian (aka, if the Azulian
|
|
||||||
// will be hostile towards the player). Boring players will not be chased after and
|
|
||||||
// the Azulian will not harm them if they make contact.
|
|
||||||
function isPlayerFood(actor) {
|
|
||||||
// Not a player or is invulnerable, or Peaceful difficulty.
|
|
||||||
if (!actor.IsPlayer() || actor.Invulnerable() || Level.Difficulty < 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// On hard mode they are hostile to any player.
|
|
||||||
if (Level.Difficulty > 0) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Azulians are friendly to Thieves and other Azulians.
|
|
||||||
if (actor.Doodad().Filename === "thief.doodad" || actor.Doodad().Title.indexOf("Azulian") > -1) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
Before Width: | Height: | Size: 864 B |
Before Width: | Height: | Size: 829 B |
Before Width: | Height: | Size: 878 B |
Before Width: | Height: | Size: 910 B |
Before Width: | Height: | Size: 853 B |
Before Width: | Height: | Size: 833 B |
Before Width: | Height: | Size: 820 B |
Before Width: | Height: | Size: 893 B |
Before Width: | Height: | Size: 816 B |
Before Width: | Height: | Size: 844 B |
Before Width: | Height: | Size: 826 B |
Before Width: | Height: | Size: 839 B |
Before Width: | Height: | Size: 803 B |
Before Width: | Height: | Size: 816 B |
Before Width: | Height: | Size: 800 B |
Before Width: | Height: | Size: 819 B |
Before Width: | Height: | Size: 829 B |
Before Width: | Height: | Size: 834 B |
Before Width: | Height: | Size: 815 B |
Before Width: | Height: | Size: 838 B |
Before Width: | Height: | Size: 1.0 KiB |
Before Width: | Height: | Size: 1004 B |
Before Width: | Height: | Size: 1.0 KiB |
Before Width: | Height: | Size: 1.0 KiB |
Before Width: | Height: | Size: 1.0 KiB |
Before Width: | Height: | Size: 1002 B |
Before Width: | Height: | Size: 994 B |
Before Width: | Height: | Size: 1.0 KiB |
Before Width: | Height: | Size: 985 B |
Before Width: | Height: | Size: 999 B |
|
@ -1,22 +0,0 @@
|
||||||
ALL: build
|
|
||||||
|
|
||||||
.PHONY: build
|
|
||||||
build:
|
|
||||||
doodad convert -t "Bird (red)" red/left-1.png red/left-2.png red/right-1.png \
|
|
||||||
red/right-2.png red/dive-left.png red/dive-right.png \
|
|
||||||
bird-red.doodad
|
|
||||||
doodad install-script bird.js bird-red.doodad
|
|
||||||
doodad edit-doodad --tag "color=red" bird-red.doodad
|
|
||||||
|
|
||||||
doodad convert -t "Bird (blue)" blue/left-1.png blue/left-2.png blue/right-1.png \
|
|
||||||
blue/right-2.png blue/dive-left.png blue/dive-right.png \
|
|
||||||
bird-blue.doodad
|
|
||||||
doodad install-script bird.js bird-blue.doodad
|
|
||||||
doodad edit-doodad --tag "color=blue" bird-blue.doodad
|
|
||||||
|
|
||||||
# Tag the category for these doodads
|
|
||||||
for i in *.doodad; do\
|
|
||||||
doodad edit-doodad --tag "category=creatures" $${i};\
|
|
||||||
done
|
|
||||||
|
|
||||||
cp *.doodad ../../../assets/doodads/
|
|
|
@ -1,274 +0,0 @@
|
||||||
// Bird (red and blue)
|
|
||||||
|
|
||||||
/*
|
|
||||||
Base A.I. behaviors (red bird) are:
|
|
||||||
|
|
||||||
- Tries to maintain original altitude in level and flies left/right.
|
|
||||||
- Divebombs the player to attack, then climbs back to its original
|
|
||||||
altitude when it hits a floor/wall.
|
|
||||||
|
|
||||||
Blue bird:
|
|
||||||
|
|
||||||
- Flies in a sine wave pattern (its target altitude fluctuates
|
|
||||||
around the bird's original placement on the level).
|
|
||||||
- Longer aggro radius to dive.
|
|
||||||
*/
|
|
||||||
|
|
||||||
let speed = 4,
|
|
||||||
Vx = Vy = 0,
|
|
||||||
color = Self.GetTag("color"), // informs our A.I. behaviors
|
|
||||||
searchStep = 12 // pixels to step while searching for a player
|
|
||||||
searchLimit = color === "blue" ? 24 : 12, // multiples of searchStep for aggro radius
|
|
||||||
altitude = Self.Position().Y, // original height in level
|
|
||||||
targetAltitude = altitude; // bird's target height to maintain
|
|
||||||
|
|
||||||
let direction = "left",
|
|
||||||
lastDirection = "left";
|
|
||||||
let states = {
|
|
||||||
flying: 0,
|
|
||||||
diving: 1,
|
|
||||||
};
|
|
||||||
let state = states.flying;
|
|
||||||
|
|
||||||
function main() {
|
|
||||||
Self.SetMobile(true);
|
|
||||||
Self.SetGravity(false);
|
|
||||||
Self.SetHitbox(0, 0, 46, 32);
|
|
||||||
Self.AddAnimation("fly-left", 100, ["left-1", "left-2"]);
|
|
||||||
Self.AddAnimation("fly-right", 100, ["right-1", "right-2"]);
|
|
||||||
|
|
||||||
// Player Character controls?
|
|
||||||
if (Self.IsPlayer()) {
|
|
||||||
return player();
|
|
||||||
}
|
|
||||||
|
|
||||||
Events.OnCollide((e) => {
|
|
||||||
// If we're diving and we hit the player, game over!
|
|
||||||
if (e.Settled && state === states.diving && e.Actor.IsPlayer()) {
|
|
||||||
FailLevel("Watch out for birds!");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (e.Actor.IsMobile() && e.Actor.HasGravity() && e.InHitbox) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Sample our X position every few frames and detect if we've hit a solid wall.
|
|
||||||
let sampleTick = 0,
|
|
||||||
sampleRate = 2,
|
|
||||||
lastSampled = Point(0, 0);
|
|
||||||
|
|
||||||
setInterval(() => {
|
|
||||||
// Run blue bird A.I. if we are blue: moves our target altitude along a sine wave.
|
|
||||||
if (color === "blue") {
|
|
||||||
AI_BlueBird();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sample how far we've moved to detect hitting a wall.
|
|
||||||
if (sampleTick % sampleRate === 0) {
|
|
||||||
let curP = Self.Position()
|
|
||||||
let delta = Math.abs(curP.X - lastSampled.X);
|
|
||||||
if (delta < 5) {
|
|
||||||
direction = direction === "right" ? "left" : "right";
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we were diving, check Y delta too for if we hit floor
|
|
||||||
if (state === states.diving && Math.abs(curP.Y - lastSampled.Y) < 5) {
|
|
||||||
state = states.flying;
|
|
||||||
}
|
|
||||||
lastSampled = curP
|
|
||||||
}
|
|
||||||
sampleTick++;
|
|
||||||
|
|
||||||
// Are we diving?
|
|
||||||
if (state === states.diving) {
|
|
||||||
Vy = speed
|
|
||||||
} else {
|
|
||||||
// If we are not flying at our original altitude, correct for that.
|
|
||||||
let curV = Self.Position();
|
|
||||||
Vy = 0.0;
|
|
||||||
if (curV.Y != targetAltitude) {
|
|
||||||
Vy = curV.Y < targetAltitude ? 1 : -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Scan for the player character and dive.
|
|
||||||
try {
|
|
||||||
AI_ScanForPlayer()
|
|
||||||
} catch (e) {
|
|
||||||
console.error("Error in AI_ScanForPlayer: %s", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Vector() requires floats, pain in the butt for JS,
|
|
||||||
// the JS API should be friendlier and custom...
|
|
||||||
let Vx = parseFloat(speed * (direction === "left" ? -1 : 1));
|
|
||||||
Self.SetVelocity(Vector(Vx, Vy));
|
|
||||||
|
|
||||||
// If diving, exit - don't edit animation.
|
|
||||||
if (state === states.diving) {
|
|
||||||
Self.ShowLayerNamed("dive-" + direction);
|
|
||||||
lastDirection = direction;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we changed directions, stop animating now so we can
|
|
||||||
// turn around quickly without moonwalking.
|
|
||||||
if (direction !== lastDirection) {
|
|
||||||
Self.StopAnimation();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!Self.IsAnimating()) {
|
|
||||||
Self.PlayAnimation("fly-" + direction, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
lastDirection = direction;
|
|
||||||
}, 100);
|
|
||||||
}
|
|
||||||
|
|
||||||
// A.I. subroutine: scan for the player character.
|
|
||||||
// The bird scans in a 45 degree angle downwards, if the
|
|
||||||
// player is seen nearby in that scan it will begin a dive.
|
|
||||||
// It's not hostile towards characters that can fly (having
|
|
||||||
// no gravity).
|
|
||||||
function AI_ScanForPlayer() {
|
|
||||||
// If Peaceful difficulty, do not attack.
|
|
||||||
if (Level.Difficulty < 0) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let stepY = searchStep, // number of pixels to skip
|
|
||||||
stepX = stepY,
|
|
||||||
limit = stepX * searchLimit, // furthest we'll scan
|
|
||||||
scanX = scanY = 0,
|
|
||||||
size = Self.Size(),
|
|
||||||
fromPoint = Self.Position();
|
|
||||||
|
|
||||||
// From what point do we begin the scan?
|
|
||||||
if (direction === 'left') {
|
|
||||||
stepX = -stepX;
|
|
||||||
fromPoint.Y += size.H;
|
|
||||||
} else {
|
|
||||||
fromPoint.Y += size.H;
|
|
||||||
fromPoint.X += size.W;
|
|
||||||
}
|
|
||||||
|
|
||||||
scanX = fromPoint.X;
|
|
||||||
scanY = fromPoint.Y;
|
|
||||||
|
|
||||||
for (let i = 0; i < limit; i += stepY) {
|
|
||||||
scanX += stepX;
|
|
||||||
scanY += stepY;
|
|
||||||
for (let actor of Actors.At(Point(scanX, scanY))) {
|
|
||||||
if (actor.IsPlayer() && actor.HasGravity()) {
|
|
||||||
state = states.diving;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sine wave controls for the Blue bird.
|
|
||||||
var sineLimit = 32,
|
|
||||||
sineCounter = 0,
|
|
||||||
sineDirection = 1,
|
|
||||||
sineFrequency = 5, // every 500ms
|
|
||||||
sineStep = 16;
|
|
||||||
|
|
||||||
// A.I. Subroutine: sine wave pattern (Blue bird).
|
|
||||||
function AI_BlueBird() {
|
|
||||||
// The main loop runs on a 100ms interval, control how frequently
|
|
||||||
// to adjust the bird's target velocity.
|
|
||||||
if (sineCounter > 0 && (sineCounter % sineFrequency) > 1) {
|
|
||||||
sineCounter++;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
sineCounter++;
|
|
||||||
|
|
||||||
targetAltitude += sineStep*sineDirection;
|
|
||||||
|
|
||||||
// Cap the distance between our starting altitude and sine-wave target.
|
|
||||||
if (targetAltitude < altitude && altitude - targetAltitude >= sineLimit) {
|
|
||||||
targetAltitude = altitude - sineLimit;
|
|
||||||
sineDirection = 1
|
|
||||||
} else if (targetAltitude > altitude && targetAltitude - altitude >= sineLimit) {
|
|
||||||
targetAltitude = altitude + sineLimit;
|
|
||||||
sineDirection = -1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If under control of the player character.
|
|
||||||
function player() {
|
|
||||||
let playerSpeed = 12,
|
|
||||||
diving = false,
|
|
||||||
falling = false;
|
|
||||||
|
|
||||||
// The player can dive by moving downwards and laterally, but
|
|
||||||
// de-cheese their ability to just sweep across the ground; if
|
|
||||||
// they aren't seen to be moving downwards, cancel the dive.
|
|
||||||
let lastPoint = Self.Position();
|
|
||||||
setInterval(() => {
|
|
||||||
let nowAt = Self.Position();
|
|
||||||
if (nowAt.Y > lastPoint.Y) {
|
|
||||||
falling = true;
|
|
||||||
} else {
|
|
||||||
falling = false;
|
|
||||||
}
|
|
||||||
lastPoint = nowAt;
|
|
||||||
}, 100);
|
|
||||||
|
|
||||||
Events.OnKeypress((ev) => {
|
|
||||||
Vx = 0;
|
|
||||||
Vy = 0;
|
|
||||||
|
|
||||||
if (ev.Right) {
|
|
||||||
direction = "right";
|
|
||||||
} else if (ev.Left) {
|
|
||||||
direction = "left";
|
|
||||||
}
|
|
||||||
|
|
||||||
// Dive!
|
|
||||||
if (ev.Down && ev.Right && falling) {
|
|
||||||
Self.StopAnimation();
|
|
||||||
Self.ShowLayerNamed("dive-right");
|
|
||||||
diving = falling;
|
|
||||||
} else if (ev.Down && ev.Left && falling) {
|
|
||||||
Self.StopAnimation();
|
|
||||||
Self.ShowLayerNamed("dive-left");
|
|
||||||
diving = falling;
|
|
||||||
} else if (ev.Right) {
|
|
||||||
// Fly right.
|
|
||||||
if (!Self.IsAnimating()) {
|
|
||||||
Self.PlayAnimation("fly-right", null);
|
|
||||||
}
|
|
||||||
Vx = playerSpeed;
|
|
||||||
diving = false;
|
|
||||||
} else if (ev.Left) {
|
|
||||||
// Fly left.
|
|
||||||
if (!Self.IsAnimating()) {
|
|
||||||
Self.PlayAnimation("fly-left", null);
|
|
||||||
}
|
|
||||||
Vx = -playerSpeed;
|
|
||||||
diving = false;
|
|
||||||
} else {
|
|
||||||
// Hover in place.
|
|
||||||
if (!Self.IsAnimating()) {
|
|
||||||
Self.PlayAnimation("fly-" + direction);
|
|
||||||
}
|
|
||||||
diving = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Player is invulnerable while diving.
|
|
||||||
Self.SetInvulnerable(diving);
|
|
||||||
});
|
|
||||||
|
|
||||||
Events.OnCollide((e) => {
|
|
||||||
// If the player is diving at an enemy mob, destroy it.
|
|
||||||
if (diving && e.Settled && e.Actor.IsMobile() && !e.Actor.Invulnerable()) {
|
|
||||||
Sound.Play("crumbly-break.wav");
|
|
||||||
e.Actor.Destroy();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
Before Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 11 KiB |
Before Width: | Height: | Size: 959 B |
Before Width: | Height: | Size: 989 B |
Before Width: | Height: | Size: 1.0 KiB |
Before Width: | Height: | Size: 1022 B |
Before Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 1.0 KiB |
|
@ -1,13 +0,0 @@
|
||||||
ALL: build
|
|
||||||
|
|
||||||
.PHONY: build
|
|
||||||
build:
|
|
||||||
doodad convert -t "Box" box-1.png box-2.png \
|
|
||||||
box-3.png box-4.png box.doodad
|
|
||||||
doodad install-script box.js box.doodad
|
|
||||||
|
|
||||||
for i in *.doodad; do \
|
|
||||||
doodad edit-doodad --tag "category=objects" $${i}; \
|
|
||||||
done
|
|
||||||
|
|
||||||
cp *.doodad ../../../assets/doodads/
|
|
Before Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 1.5 KiB |
|
@ -1,73 +0,0 @@
|
||||||
// Pushable Box.
|
|
||||||
|
|
||||||
const speed = 4,
|
|
||||||
size = 75;
|
|
||||||
|
|
||||||
function main() {
|
|
||||||
Self.SetMobile(true);
|
|
||||||
Self.SetInvulnerable(true);
|
|
||||||
Self.SetGravity(true);
|
|
||||||
Self.SetHitbox(0, 0, size, size);
|
|
||||||
|
|
||||||
Events.OnCollide((e) => {
|
|
||||||
// Ignore events from neighboring Boxes.
|
|
||||||
if (e.Actor.Actor.Filename === "box.doodad") {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (e.Actor.IsMobile() && e.InHitbox) {
|
|
||||||
let overlap = e.Overlap;
|
|
||||||
|
|
||||||
if (overlap.Y === 0 && !(overlap.X === 0 && overlap.W < 5) && !(overlap.X === size)) {
|
|
||||||
// Be sure to position them snug on top.
|
|
||||||
// TODO: this might be a nice general solution in the
|
|
||||||
// collision detector...
|
|
||||||
e.Actor.MoveTo(Point(
|
|
||||||
e.Actor.Position().X,
|
|
||||||
Self.Position().Y - e.Actor.Hitbox().Y - e.Actor.Hitbox().H - 2,
|
|
||||||
))
|
|
||||||
e.Actor.SetGrounded(true);
|
|
||||||
return false;
|
|
||||||
} else if (overlap.Y === size) {
|
|
||||||
// From the bottom, boop it up.
|
|
||||||
Self.SetVelocity(Vector(0, -speed * 2))
|
|
||||||
}
|
|
||||||
|
|
||||||
// If touching from the sides, slide away.
|
|
||||||
if (overlap.X === 0) {
|
|
||||||
Self.SetVelocity(Vector(speed, 0))
|
|
||||||
} else if (overlap.X === size) {
|
|
||||||
Self.SetVelocity(Vector(-speed, 0))
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
Events.OnLeave(function (e) {
|
|
||||||
Self.SetVelocity(Vector(0, 0));
|
|
||||||
});
|
|
||||||
|
|
||||||
// When we receive power, we reset to our original position.
|
|
||||||
let origPoint = Self.Position();
|
|
||||||
Message.Subscribe("power", (powered) => {
|
|
||||||
Self.MoveTo(origPoint);
|
|
||||||
Self.SetVelocity(Vector(0, 0));
|
|
||||||
});
|
|
||||||
|
|
||||||
// Start animation on a loop.
|
|
||||||
animate();
|
|
||||||
}
|
|
||||||
|
|
||||||
function animate() {
|
|
||||||
Self.AddAnimation("animate", 100, [0, 1, 2, 3, 2, 1]);
|
|
||||||
|
|
||||||
let running = false;
|
|
||||||
setInterval(() => {
|
|
||||||
if (!running) {
|
|
||||||
running = true;
|
|
||||||
Self.PlayAnimation("animate", function () {
|
|
||||||
running = false;
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}, 100);
|
|
||||||
}
|
|
|
@ -1,15 +0,0 @@
|
||||||
ALL: build
|
|
||||||
|
|
||||||
.PHONY: build
|
|
||||||
build:
|
|
||||||
doodad convert -t "Boy" stand-right.png stand-left.png \
|
|
||||||
walk-right-1.png walk-right-2.png walk-right-3.png \
|
|
||||||
walk-left-1.png walk-left-2.png walk-left-3.png \
|
|
||||||
idle-right-1.png idle-right-2.png idle-right-3.png \
|
|
||||||
idle-left-1.png idle-left-2.png idle-left-3.png \
|
|
||||||
boy.doodad
|
|
||||||
doodad install-script boy.js boy.doodad
|
|
||||||
|
|
||||||
doodad edit-doodad --tag "category=creatures" boy.doodad
|
|
||||||
|
|
||||||
cp *.doodad ../../../assets/doodads/
|
|
|
@ -1,62 +0,0 @@
|
||||||
const playerSpeed = 12;
|
|
||||||
|
|
||||||
let Vx = Vy = 0,
|
|
||||||
walking = false,
|
|
||||||
direction = "right",
|
|
||||||
lastDirection = direction;
|
|
||||||
|
|
||||||
function main() {
|
|
||||||
Self.SetMobile(true);
|
|
||||||
Self.SetInventory(true);
|
|
||||||
Self.SetGravity(true);
|
|
||||||
Self.SetHitbox(0, 0, 32, 52);
|
|
||||||
Self.AddAnimation("walk-left", 200, ["stand-left", "walk-left-1", "walk-left-2", "walk-left-3", "walk-left-2", "walk-left-1"]);
|
|
||||||
Self.AddAnimation("walk-right", 200, ["stand-right", "walk-right-1", "walk-right-2", "walk-right-3", "walk-right-2", "walk-right-1"]);
|
|
||||||
Self.AddAnimation("idle-left", 200, ["idle-left-1", "idle-left-2", "idle-left-3", "idle-left-2"]);
|
|
||||||
Self.AddAnimation("idle-right", 200, ["idle-right-1", "idle-right-2", "idle-right-3", "idle-right-2"]);
|
|
||||||
|
|
||||||
// If the player suddenly changes direction, reset the animation state to quickly switch over.
|
|
||||||
let lastVelocity = Vector(0, 0);
|
|
||||||
|
|
||||||
Events.OnKeypress((ev) => {
|
|
||||||
Vx = 0;
|
|
||||||
Vy = 0;
|
|
||||||
|
|
||||||
let curVelocity = Self.GetVelocity();
|
|
||||||
if ((lastVelocity.X < 0 && curVelocity.X > 0) ||
|
|
||||||
(lastVelocity.X > 0 && curVelocity.X < 0)) {
|
|
||||||
Self.StopAnimation();
|
|
||||||
}
|
|
||||||
lastVelocity = curVelocity;
|
|
||||||
lastDirection = direction;
|
|
||||||
|
|
||||||
let wasWalking = walking;
|
|
||||||
if (ev.Right) {
|
|
||||||
direction = "right";
|
|
||||||
Vx = playerSpeed;
|
|
||||||
walking = true;
|
|
||||||
} else if (ev.Left) {
|
|
||||||
direction = "left";
|
|
||||||
Vx = -playerSpeed;
|
|
||||||
walking = true;
|
|
||||||
} else {
|
|
||||||
// Has stopped walking!
|
|
||||||
walking = false;
|
|
||||||
stoppedWalking = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Should we stop animating? (changed state)
|
|
||||||
if (direction !== lastDirection || wasWalking !== walking) {
|
|
||||||
Self.StopAnimation();
|
|
||||||
}
|
|
||||||
|
|
||||||
// And play what animation?
|
|
||||||
if (!Self.IsAnimating()) {
|
|
||||||
if (walking) {
|
|
||||||
Self.PlayAnimation("walk-"+direction, null);
|
|
||||||
} else {
|
|
||||||
Self.PlayAnimation("idle-"+direction, null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
Before Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 3.3 KiB |
|
@ -1,114 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# Build all the doodads from their source files.
|
|
||||||
if [[ ! -d "./azulian" ]]; then
|
|
||||||
echo Run this script from the dev-assets/doodads/ working directory.
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
mkdir -p ../../assets/doodads
|
|
||||||
|
|
||||||
boy() {
|
|
||||||
cd boy/
|
|
||||||
make
|
|
||||||
cd ..
|
|
||||||
|
|
||||||
cd thief/
|
|
||||||
make
|
|
||||||
cd ..
|
|
||||||
}
|
|
||||||
|
|
||||||
buttons() {
|
|
||||||
cd buttons/
|
|
||||||
make
|
|
||||||
cd ..
|
|
||||||
}
|
|
||||||
|
|
||||||
switches() {
|
|
||||||
cd switches/
|
|
||||||
make
|
|
||||||
cd ..
|
|
||||||
}
|
|
||||||
|
|
||||||
doors() {
|
|
||||||
cd doors/
|
|
||||||
make
|
|
||||||
cd ..
|
|
||||||
|
|
||||||
cd gems/
|
|
||||||
make
|
|
||||||
cd ..
|
|
||||||
}
|
|
||||||
|
|
||||||
trapdoors() {
|
|
||||||
cd trapdoors/
|
|
||||||
make
|
|
||||||
cd ..
|
|
||||||
}
|
|
||||||
|
|
||||||
azulians() {
|
|
||||||
cd azulian/
|
|
||||||
make
|
|
||||||
cd ..
|
|
||||||
}
|
|
||||||
|
|
||||||
mobs() {
|
|
||||||
cd bird/
|
|
||||||
make
|
|
||||||
cd ..
|
|
||||||
}
|
|
||||||
|
|
||||||
objects() {
|
|
||||||
cd objects/
|
|
||||||
make
|
|
||||||
cd ..
|
|
||||||
|
|
||||||
cd box/
|
|
||||||
make
|
|
||||||
cd ..
|
|
||||||
|
|
||||||
cd crumbly-floor/
|
|
||||||
make
|
|
||||||
cd ..
|
|
||||||
|
|
||||||
cd regions/
|
|
||||||
make
|
|
||||||
cd ..
|
|
||||||
}
|
|
||||||
|
|
||||||
onoff() {
|
|
||||||
cd on-off/
|
|
||||||
make
|
|
||||||
cd ..
|
|
||||||
}
|
|
||||||
|
|
||||||
warpdoor() {
|
|
||||||
cd warp-door/
|
|
||||||
make
|
|
||||||
cd ..
|
|
||||||
}
|
|
||||||
|
|
||||||
creatures() {
|
|
||||||
cd snake/
|
|
||||||
make
|
|
||||||
cd ..
|
|
||||||
|
|
||||||
cd crusher/
|
|
||||||
make
|
|
||||||
cd ..
|
|
||||||
}
|
|
||||||
|
|
||||||
boy
|
|
||||||
buttons
|
|
||||||
switches
|
|
||||||
doors
|
|
||||||
trapdoors
|
|
||||||
azulians
|
|
||||||
mobs
|
|
||||||
objects
|
|
||||||
onoff
|
|
||||||
warpdoor
|
|
||||||
creatures
|
|
||||||
doodad edit-doodad -quiet -lock -author "Noah" ../../assets/doodads/*.doodad
|
|
||||||
doodad edit-doodad ../../assets/doodads/azu-blu.doodad
|
|
||||||
doodad edit-doodad -hide ../../assets/doodads/boy.doodad
|
|
|
@ -1,19 +0,0 @@
|
||||||
ALL: build
|
|
||||||
|
|
||||||
.PHONY: build
|
|
||||||
build:
|
|
||||||
doodad convert -t "Sticky Button" sticky1.png sticky2.png button-sticky.doodad
|
|
||||||
doodad install-script sticky.js button-sticky.doodad
|
|
||||||
|
|
||||||
doodad convert -t "Button" button1.png button2.png button.doodad
|
|
||||||
doodad install-script button.js button.doodad
|
|
||||||
|
|
||||||
doodad convert -t "Button Type B" typeB1.png typeB2.png button-typeB.doodad
|
|
||||||
doodad install-script button.js button-typeB.doodad
|
|
||||||
|
|
||||||
# Tag the category for these doodads
|
|
||||||
for i in *.doodad; do\
|
|
||||||
doodad edit-doodad --tag "category=gizmos" $${i};\
|
|
||||||
done
|
|
||||||
|
|
||||||
cp *.doodad ../../../assets/doodads/
|
|
|
@ -1,12 +0,0 @@
|
||||||
# Button Doodads
|
|
||||||
|
|
||||||
```bash
|
|
||||||
doodad convert -t "Sticky Button" sticky1.png sticky2.png sticky-button.doodad
|
|
||||||
doodad install-script sticky.js sticky-button.doodad
|
|
||||||
|
|
||||||
doodad convert -t "Button" button1.png button2.png button.doodad
|
|
||||||
doodad install-script button.js button.doodad
|
|
||||||
|
|
||||||
doodad convert -t "Button Type B" typeB1.png typeB2.png button-typeB.doodad
|
|
||||||
doodad install-script button.js button-typeB.doodad
|
|
||||||
```
|
|
|
@ -1,60 +0,0 @@
|
||||||
function main() {
|
|
||||||
let timer = 0;
|
|
||||||
let pressed = false;
|
|
||||||
|
|
||||||
// Has a linked Sticky Button been pressed permanently down?
|
|
||||||
let stickyDown = false;
|
|
||||||
Message.Subscribe("sticky:down", (down) => {
|
|
||||||
stickyDown = down;
|
|
||||||
Self.ShowLayer(stickyDown ? 1 : 0);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Track who all is colliding with us.
|
|
||||||
let colliders = {};
|
|
||||||
|
|
||||||
Events.OnCollide((e) => {
|
|
||||||
if (!e.Settled) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (colliders[e.Actor.ID()] == undefined) {
|
|
||||||
colliders[e.Actor.ID()] = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If a linked Sticky Button is pressed, button stays down too and
|
|
||||||
// doesn't interact.
|
|
||||||
if (stickyDown) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify they've touched the button.
|
|
||||||
if (e.Overlap.Y + e.Overlap.H < 24) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!pressed && !stickyDown) {
|
|
||||||
Sound.Play("button-down.wav")
|
|
||||||
Message.Publish("power", true);
|
|
||||||
pressed = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if (timer > 0) {
|
|
||||||
clearTimeout(timer);
|
|
||||||
}
|
|
||||||
|
|
||||||
Self.ShowLayer(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
Events.OnLeave((e) => {
|
|
||||||
delete colliders[e.Actor.ID()];
|
|
||||||
|
|
||||||
if (Object.keys(colliders).length === 0 && !stickyDown) {
|
|
||||||
Sound.Play("button-up.wav")
|
|
||||||
Self.ShowLayer(0);
|
|
||||||
Message.Publish("power", false);
|
|
||||||
timer = 0;
|
|
||||||
pressed = false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
Before Width: | Height: | Size: 769 B |
Before Width: | Height: | Size: 728 B |
|
@ -1,35 +0,0 @@
|
||||||
function main() {
|
|
||||||
let pressed = false;
|
|
||||||
|
|
||||||
// When a sticky button receives power, it pops back up.
|
|
||||||
Message.Subscribe("power", (powered) => {
|
|
||||||
if (powered && pressed) {
|
|
||||||
Self.ShowLayer(0);
|
|
||||||
pressed = false;
|
|
||||||
Sound.Play("button-up.wav")
|
|
||||||
Message.Publish("power", false);
|
|
||||||
Message.Publish("sticky:down", false);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
Events.OnCollide((e) => {
|
|
||||||
if (!e.Settled) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pressed) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify they've touched the button.
|
|
||||||
if (e.Overlap.Y + e.Overlap.H < 24) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Sound.Play("button-down.wav")
|
|
||||||
Self.ShowLayer(1);
|
|
||||||
pressed = true;
|
|
||||||
Message.Publish("power", true);
|
|
||||||
Message.Publish("sticky:down", true);
|
|
||||||
});
|
|
||||||
}
|
|
Before Width: | Height: | Size: 767 B |
Before Width: | Height: | Size: 726 B |
Before Width: | Height: | Size: 736 B |
Before Width: | Height: | Size: 695 B |
|
@ -1,12 +0,0 @@
|
||||||
ALL: build
|
|
||||||
|
|
||||||
.PHONY: build
|
|
||||||
build:
|
|
||||||
doodad convert -t "Crumbly Floor" floor.png shake1.png shake2.png \
|
|
||||||
fall1.png fall2.png fall3.png fall4.png fallen.png \
|
|
||||||
crumbly-floor.doodad
|
|
||||||
doodad install-script crumbly-floor.js crumbly-floor.doodad
|
|
||||||
for i in *.doodad; do\
|
|
||||||
doodad edit-doodad --tag "category=objects" $${i};\
|
|
||||||
done
|
|
||||||
cp *.doodad ../../../assets/doodads/
|
|
|
@ -1,59 +0,0 @@
|
||||||
// Crumbly Floor.
|
|
||||||
function main() {
|
|
||||||
Self.SetHitbox(0, 0, 98, 11);
|
|
||||||
|
|
||||||
Self.AddAnimation("shake", 100, ["shake1", "shake2", "floor", "shake1", "shake2", "floor"]);
|
|
||||||
Self.AddAnimation("fall", 100, ["fall1", "fall2", "fall3", "fall4"]);
|
|
||||||
|
|
||||||
// Recover time for the floor to respawn.
|
|
||||||
let recover = 5000;
|
|
||||||
|
|
||||||
// States of the floor.
|
|
||||||
let stateSolid = 0;
|
|
||||||
let stateShaking = 1;
|
|
||||||
let stateFalling = 2;
|
|
||||||
let stateFallen = 3;
|
|
||||||
let state = stateSolid;
|
|
||||||
|
|
||||||
Events.OnCollide((e) => {
|
|
||||||
|
|
||||||
// If the floor is falling, the player passes right thru.
|
|
||||||
if (state === stateFalling || state === stateFallen) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Floor is solid until it begins to fall.
|
|
||||||
if (e.InHitbox && (state === stateSolid || state === stateShaking)) {
|
|
||||||
// Only activate when touched from the top.
|
|
||||||
if (e.Overlap.Y > 0) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If movement is not settled, be solid.
|
|
||||||
if (!e.Settled) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Begin the animation sequence if we're in the solid state.
|
|
||||||
if (state === stateSolid) {
|
|
||||||
state = stateShaking;
|
|
||||||
Self.PlayAnimation("shake", () => {
|
|
||||||
state = stateFalling;
|
|
||||||
Self.PlayAnimation("fall", () => {
|
|
||||||
Sound.Play("crumbly-break.wav")
|
|
||||||
state = stateFallen;
|
|
||||||
Self.ShowLayerNamed("fallen");
|
|
||||||
|
|
||||||
// Recover after a while.
|
|
||||||
setTimeout(() => {
|
|
||||||
Self.ShowLayer(0);
|
|
||||||
state = stateSolid;
|
|
||||||
}, recover);
|
|
||||||
});
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
Before Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 5.2 KiB |
Before Width: | Height: | Size: 1.0 KiB |
Before Width: | Height: | Size: 1.0 KiB |
Before Width: | Height: | Size: 1.0 KiB |
|
@ -1,14 +0,0 @@
|
||||||
ALL: build
|
|
||||||
|
|
||||||
.PHONY: build
|
|
||||||
build:
|
|
||||||
doodad convert -t "Crusher" sleep.png peek-left.png peek-right.png \
|
|
||||||
angry.png ouch.png crusher.doodad
|
|
||||||
doodad install-script crusher.js crusher.doodad
|
|
||||||
|
|
||||||
# Tag the category for these doodads
|
|
||||||
for i in *.doodad; do\
|
|
||||||
doodad edit-doodad --tag "category=creatures" $${i};\
|
|
||||||
done
|
|
||||||
|
|
||||||
cp *.doodad ../../../assets/doodads/
|
|
Before Width: | Height: | Size: 2.3 KiB |
|
@ -1,207 +0,0 @@
|
||||||
// Crusher
|
|
||||||
|
|
||||||
/*
|
|
||||||
A.I. Behaviors:
|
|
||||||
|
|
||||||
- Sleeps and hangs in the air in a high place.
|
|
||||||
- When a player gets nearby, it begins "peeking" in their direction.
|
|
||||||
- When the player is below, tries to drop and crush them or any
|
|
||||||
other mobile doodad.
|
|
||||||
- The top edge is safe to walk on and ride back up like an elevator.
|
|
||||||
*/
|
|
||||||
|
|
||||||
let direction = "left",
|
|
||||||
dropSpeed = 12,
|
|
||||||
riseSpeed = 4,
|
|
||||||
watchRadius = 300, // player nearby distance to start peeking
|
|
||||||
fallRadius = 120, // player distance before it drops
|
|
||||||
helmetThickness = 48, // safe solid hitbox height
|
|
||||||
fireThickness = 12, // dangerous bottom thickness
|
|
||||||
targetAltitude = Self.Position()
|
|
||||||
lastAltitude = targetAltitude.Y
|
|
||||||
size = Self.Size();
|
|
||||||
|
|
||||||
const states = {
|
|
||||||
idle: 0,
|
|
||||||
peeking: 1,
|
|
||||||
drop: 2,
|
|
||||||
falling: 3,
|
|
||||||
hit: 4,
|
|
||||||
rising: 5,
|
|
||||||
};
|
|
||||||
let state = states.idle;
|
|
||||||
|
|
||||||
function main() {
|
|
||||||
Self.SetMobile(true);
|
|
||||||
Self.SetGravity(false);
|
|
||||||
Self.SetInvulnerable(true);
|
|
||||||
Self.SetHitbox(5, 2, 90, 73);
|
|
||||||
Self.AddAnimation("hit", 50,
|
|
||||||
["angry", "ouch", "angry", "angry", "angry", "angry",
|
|
||||||
"sleep", "sleep", "sleep", "sleep", "sleep", "sleep",
|
|
||||||
"sleep", "sleep", "sleep", "sleep", "sleep", "sleep",
|
|
||||||
"sleep", "sleep", "sleep", "sleep", "sleep", "sleep"],
|
|
||||||
)
|
|
||||||
|
|
||||||
// Player Character controls?
|
|
||||||
if (Self.IsPlayer()) {
|
|
||||||
return player();
|
|
||||||
}
|
|
||||||
|
|
||||||
let hitbox = Self.Hitbox();
|
|
||||||
|
|
||||||
Events.OnCollide((e) => {
|
|
||||||
// The bottom is deadly if falling.
|
|
||||||
if (state === states.falling || state === states.hit && e.Settled) {
|
|
||||||
if (e.Actor.IsMobile() && e.InHitbox && !e.Actor.Invulnerable()) {
|
|
||||||
if (e.Overlap.H > 72) {
|
|
||||||
if (e.Actor.IsPlayer()) {
|
|
||||||
FailLevel("Don't get crushed!");
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
e.Actor.Destroy();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Our top edge is always solid.
|
|
||||||
if (e.Actor.IsPlayer() && e.InHitbox) {
|
|
||||||
if (e.Overlap.Y < helmetThickness) {
|
|
||||||
// Be sure to position them snug on top.
|
|
||||||
// TODO: this might be a nice general solution in the
|
|
||||||
// collision detector...
|
|
||||||
e.Actor.MoveTo(Point(
|
|
||||||
e.Actor.Position().X,
|
|
||||||
Self.Position().Y - e.Actor.Hitbox().Y - e.Actor.Hitbox().H,
|
|
||||||
))
|
|
||||||
e.Actor.SetGrounded(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// The whole hitbox is ordinarily solid.
|
|
||||||
if (state !== state.falling) {
|
|
||||||
if (e.Actor.IsMobile() && e.InHitbox) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
setInterval(() => {
|
|
||||||
// Find the player.
|
|
||||||
let player = Actors.FindPlayer(),
|
|
||||||
playerPoint = player.Position(),
|
|
||||||
point = Self.Position(),
|
|
||||||
delta = 0,
|
|
||||||
nearby = false,
|
|
||||||
below = false;
|
|
||||||
|
|
||||||
// Face the player.
|
|
||||||
if (playerPoint.X < point.X + (size.W / 2)) {
|
|
||||||
direction = "left";
|
|
||||||
delta = Math.abs(playerPoint.X - (point.X + (size.W/2)));
|
|
||||||
}
|
|
||||||
else if (playerPoint.X > point.X + (size.W / 2)) {
|
|
||||||
direction = "right";
|
|
||||||
delta = Math.abs(playerPoint.X - (point.X + (size.W/2)));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (delta < watchRadius) {
|
|
||||||
nearby = true;
|
|
||||||
}
|
|
||||||
if (delta < fallRadius) {
|
|
||||||
// Check if the player is below us.
|
|
||||||
if (playerPoint.Y > point.Y + size.H) {
|
|
||||||
below = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (state) {
|
|
||||||
case states.idle:
|
|
||||||
if (nearby) {
|
|
||||||
Self.ShowLayerNamed("peek-"+direction);
|
|
||||||
} else {
|
|
||||||
Self.ShowLayerNamed("sleep");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (below) {
|
|
||||||
state = states.drop;
|
|
||||||
} else if (nearby) {
|
|
||||||
state = states.peeking;
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
case states.peeking:
|
|
||||||
if (nearby) {
|
|
||||||
Self.ShowLayerNamed("peek-"+direction);
|
|
||||||
} else {
|
|
||||||
state = states.idle;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (below) {
|
|
||||||
state = states.drop;
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
case states.drop:
|
|
||||||
// Begin the fall.
|
|
||||||
Self.ShowLayerNamed("angry");
|
|
||||||
Self.SetVelocity(Vector(0.0, dropSpeed));
|
|
||||||
lastAltitude = -point.Y;
|
|
||||||
state = states.falling;
|
|
||||||
case states.falling:
|
|
||||||
Self.ShowLayerNamed("angry");
|
|
||||||
Self.SetVelocity(Vector(0.0, dropSpeed));
|
|
||||||
|
|
||||||
// Landed?
|
|
||||||
if (point.Y === lastAltitude) {
|
|
||||||
Sound.Play("crumbly-break.wav")
|
|
||||||
state = states.hit;
|
|
||||||
Self.PlayAnimation("hit", () => {
|
|
||||||
state = states.rising;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case states.hit:
|
|
||||||
// A transitory state while the hit animation
|
|
||||||
// plays out.
|
|
||||||
break;
|
|
||||||
case states.rising:
|
|
||||||
Self.ShowLayerNamed("sleep");
|
|
||||||
Self.SetVelocity(Vector(0, -riseSpeed));
|
|
||||||
|
|
||||||
point = Self.Position();
|
|
||||||
if (point.Y <= targetAltitude.Y+4 || point.Y === lastAltitude.Y) {
|
|
||||||
Self.MoveTo(targetAltitude);
|
|
||||||
Self.SetVelocity(Vector(0, 0))
|
|
||||||
state = states.idle;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
lastAltitude = point.Y;
|
|
||||||
}, 100);
|
|
||||||
}
|
|
||||||
|
|
||||||
// If under control of the player character.
|
|
||||||
function player() {
|
|
||||||
Events.OnKeypress((ev) => {
|
|
||||||
if (ev.Right) {
|
|
||||||
direction = "right";
|
|
||||||
} else if (ev.Left) {
|
|
||||||
direction = "left";
|
|
||||||
}
|
|
||||||
|
|
||||||
// Jump!
|
|
||||||
if (ev.Down) {
|
|
||||||
Self.ShowLayerNamed("angry");
|
|
||||||
return;
|
|
||||||
} else if (ev.Right && ev.Left) {
|
|
||||||
Self.ShowLayerNamed("ouch");
|
|
||||||
} else if (ev.Right || ev.Left) {
|
|
||||||
Self.ShowLayerNamed("peek-"+direction);
|
|
||||||
} else {
|
|
||||||
Self.ShowLayerNamed("sleep");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
Before Width: | Height: | Size: 2.2 KiB |
Before Width: | Height: | Size: 2.2 KiB |
Before Width: | Height: | Size: 2.2 KiB |
Before Width: | Height: | Size: 2.1 KiB |
|
@ -1,7 +0,0 @@
|
||||||
SHELL = /bin/bash
|
|
||||||
|
|
||||||
ALL: build
|
|
||||||
|
|
||||||
.PHONY: build
|
|
||||||
build:
|
|
||||||
./build.sh
|
|
|
@ -1,15 +0,0 @@
|
||||||
# Button Doodads
|
|
||||||
|
|
||||||
```bash
|
|
||||||
doodad convert -t "Red Door" red1.png red2.png red-door.doodad
|
|
||||||
doodad convert -t "Blue Door" blue1.png blue2.png blue-door.doodad
|
|
||||||
doodad convert -t "Green Door" green1.png green2.png green-door.doodad
|
|
||||||
doodad convert -t "Yellow Door" yellow1.png yellow2.png yellow-door.doodad
|
|
||||||
|
|
||||||
doodad convert -t "Red Key" red-key.png red-key.doodad
|
|
||||||
doodad convert -t "Blue Key" blue-key.png blue-key.doodad
|
|
||||||
doodad convert -t "Green Key" green-key.png green-key.doodad
|
|
||||||
doodad convert -t "Yellow Key" yellow-key.png yellow-key.doodad
|
|
||||||
|
|
||||||
doodad convert -t "Electric Door" electric{1,2,3,4}.png electric-door.doodad
|
|
||||||
```
|
|
Before Width: | Height: | Size: 1.0 KiB |