Compare commits
5 Commits
0ed5d6ae0c
...
142ed7d4c5
Author | SHA1 | Date |
---|---|---|
Noah | 142ed7d4c5 | |
Noah | 94c4ed24d2 | |
Noah | 7005786a31 | |
Noah | 4f5ea15e46 | |
Noah | 50980caebb |
|
@ -2,9 +2,11 @@
|
|||
|
||||
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";
|
||||
|
@ -14,7 +16,9 @@ if (color === 'white') {
|
|||
aggroX = 1000;
|
||||
aggroY = 400;
|
||||
playerSpeed = 8;
|
||||
swimSpeed = playerSpeed * 0.4;
|
||||
jumpSpeed = 20;
|
||||
swimJumpSpeed = jumpSpeed * 0.4;
|
||||
}
|
||||
|
||||
function setupAnimations(color) {
|
||||
|
@ -30,6 +34,9 @@ function setupAnimations(color) {
|
|||
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);
|
||||
|
@ -95,8 +102,25 @@ function main() {
|
|||
sampleTick++;
|
||||
}
|
||||
|
||||
let Vx = parseFloat(playerSpeed * (direction === "left" ? -1 : 1)),
|
||||
Vy = jump && Self.Grounded() ? parseFloat(-jumpSpeed) : Self.GetVelocity().Y;
|
||||
// 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
|
||||
|
|
|
@ -19,7 +19,14 @@ function main() {
|
|||
let overlap = e.Overlap;
|
||||
|
||||
if (overlap.Y === 0 && !(overlap.X === 0 && overlap.W < 5) && !(overlap.X === size)) {
|
||||
// Standing on top, ignore.
|
||||
// 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.
|
||||
|
|
|
@ -5,6 +5,8 @@ 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
|
||||
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
const playerSpeed = 12;
|
||||
|
||||
let Vx = Vy = 0,
|
||||
animating = false,
|
||||
animStart = animEnd = 0;
|
||||
walking = false,
|
||||
direction = "right",
|
||||
lastDirection = direction;
|
||||
|
||||
function main() {
|
||||
Self.SetMobile(true);
|
||||
|
@ -11,6 +12,8 @@ function main() {
|
|||
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);
|
||||
|
@ -25,20 +28,35 @@ function main() {
|
|||
Self.StopAnimation();
|
||||
}
|
||||
lastVelocity = curVelocity;
|
||||
lastDirection = direction;
|
||||
|
||||
let wasWalking = walking;
|
||||
if (ev.Right) {
|
||||
if (!Self.IsAnimating()) {
|
||||
Self.PlayAnimation("walk-right", null);
|
||||
}
|
||||
direction = "right";
|
||||
Vx = playerSpeed;
|
||||
walking = true;
|
||||
} else if (ev.Left) {
|
||||
if (!Self.IsAnimating()) {
|
||||
Self.PlayAnimation("walk-left", null);
|
||||
}
|
||||
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();
|
||||
animating = false;
|
||||
}
|
||||
|
||||
// And play what animation?
|
||||
if (!Self.IsAnimating()) {
|
||||
if (walking) {
|
||||
Self.PlayAnimation("walk-"+direction, null);
|
||||
} else {
|
||||
Self.PlayAnimation("idle-"+direction, null);
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
After Width: | Height: | Size: 12 KiB |
After Width: | Height: | Size: 12 KiB |
After Width: | Height: | Size: 12 KiB |
After Width: | Height: | Size: 12 KiB |
After Width: | Height: | Size: 12 KiB |
After Width: | Height: | Size: 12 KiB |
|
@ -34,6 +34,10 @@ doors() {
|
|||
cd doors/
|
||||
make
|
||||
cd ..
|
||||
|
||||
cd gems/
|
||||
make
|
||||
cd ..
|
||||
}
|
||||
|
||||
trapdoors() {
|
||||
|
@ -84,6 +88,16 @@ warpdoor() {
|
|||
cd ..
|
||||
}
|
||||
|
||||
creatures() {
|
||||
cd snake/
|
||||
make
|
||||
cd ..
|
||||
|
||||
cd crusher/
|
||||
make
|
||||
cd ..
|
||||
}
|
||||
|
||||
boy
|
||||
buttons
|
||||
switches
|
||||
|
@ -94,6 +108,7 @@ 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
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
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/
|
After Width: | Height: | Size: 2.3 KiB |
|
@ -0,0 +1,207 @@
|
|||
// 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");
|
||||
}
|
||||
});
|
||||
}
|
After Width: | Height: | Size: 2.2 KiB |
After Width: | Height: | Size: 2.2 KiB |
After Width: | Height: | Size: 2.2 KiB |
After Width: | Height: | Size: 2.1 KiB |
|
@ -0,0 +1,58 @@
|
|||
ALL: build
|
||||
|
||||
.PHONY: build
|
||||
build:
|
||||
###
|
||||
# Gemstones
|
||||
###
|
||||
|
||||
doodad convert -t "Gemstone (Green)" green-1.png green-2.png green-3.png green-4.png \
|
||||
gem-green.doodad
|
||||
doodad install-script gemstone.js gem-green.doodad
|
||||
doodad edit-doodad --tag "color=green" gem-green.doodad
|
||||
|
||||
doodad convert -t "Gemstone (Red)" red-1.png red-2.png red-3.png red-4.png \
|
||||
gem-red.doodad
|
||||
doodad install-script gemstone.js gem-red.doodad
|
||||
doodad edit-doodad --tag "color=red" gem-red.doodad
|
||||
|
||||
doodad convert -t "Gemstone (Blue)" blue-1.png blue-2.png blue-3.png blue-4.png \
|
||||
gem-blue.doodad
|
||||
doodad install-script gemstone.js gem-blue.doodad
|
||||
doodad edit-doodad --tag "color=blue" gem-blue.doodad
|
||||
|
||||
doodad convert -t "Gemstone (Yellow)" yellow-1.png yellow-2.png yellow-3.png yellow-4.png \
|
||||
gem-yellow.doodad
|
||||
doodad install-script gemstone.js gem-yellow.doodad
|
||||
doodad edit-doodad --tag "color=yellow" gem-yellow.doodad
|
||||
|
||||
###
|
||||
# Totems
|
||||
###
|
||||
|
||||
doodad convert -t "Gemstone Totem (Green)" totem-green-1.png totem-green-2.png totem-green-3.png \
|
||||
totem-green-4.png totem-green-0.png gem-totem-green.doodad
|
||||
doodad install-script totem.js gem-totem-green.doodad
|
||||
doodad edit-doodad --tag "color=green" gem-totem-green.doodad
|
||||
|
||||
doodad convert -t "Gemstone Totem (Yellow)" totem-yellow-1.png totem-yellow-2.png totem-yellow-3.png \
|
||||
totem-yellow-4.png totem-yellow-0.png gem-totem-yellow.doodad
|
||||
doodad install-script totem.js gem-totem-yellow.doodad
|
||||
doodad edit-doodad --tag "color=yellow" gem-totem-yellow.doodad
|
||||
|
||||
doodad convert -t "Gemstone Totem (Blue)" totem-blue-1.png totem-blue-2.png totem-blue-3.png \
|
||||
totem-blue-4.png totem-blue-0.png gem-totem-blue.doodad
|
||||
doodad install-script totem.js gem-totem-blue.doodad
|
||||
doodad edit-doodad --tag "color=blue" gem-totem-blue.doodad
|
||||
|
||||
doodad convert -t "Gemstone Totem (Red)" totem-red-1.png totem-red-2.png totem-red-3.png \
|
||||
totem-red-4.png totem-red-0.png gem-totem-red.doodad
|
||||
doodad install-script totem.js gem-totem-red.doodad
|
||||
doodad edit-doodad --tag "color=red" gem-totem-red.doodad
|
||||
|
||||
# Tag the category for these doodads
|
||||
for i in *.doodad; do\
|
||||
doodad edit-doodad --tag "category=doors" $${i};\
|
||||
done
|
||||
|
||||
cp *.doodad ../../../assets/doodads/
|
After Width: | Height: | Size: 891 B |
After Width: | Height: | Size: 927 B |
After Width: | Height: | Size: 903 B |
After Width: | Height: | Size: 835 B |
|
@ -0,0 +1,24 @@
|
|||
// Gem stone collectibles/keys.
|
||||
|
||||
const color = Self.GetTag("color"),
|
||||
shimmerFreq = 1000;
|
||||
|
||||
function main() {
|
||||
Self.SetMobile(true);
|
||||
Self.SetGravity(true);
|
||||
|
||||
Self.AddAnimation("shimmer", 100, [0, 1, 2, 3, 0]);
|
||||
Events.OnCollide((e) => {
|
||||
if (e.Settled) {
|
||||
if (e.Actor.HasInventory()) {
|
||||
Sound.Play("item-get.wav")
|
||||
e.Actor.AddItem(Self.Filename, 1);
|
||||
Self.Destroy();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
setInterval(() => {
|
||||
Self.PlayAnimation("shimmer", null);
|
||||
}, shimmerFreq);
|
||||
}
|
After Width: | Height: | Size: 785 B |
After Width: | Height: | Size: 807 B |
After Width: | Height: | Size: 864 B |
After Width: | Height: | Size: 835 B |
After Width: | Height: | Size: 711 B |
After Width: | Height: | Size: 702 B |
After Width: | Height: | Size: 712 B |
After Width: | Height: | Size: 724 B |
After Width: | Height: | Size: 798 B |
After Width: | Height: | Size: 958 B |
After Width: | Height: | Size: 1005 B |
After Width: | Height: | Size: 987 B |
After Width: | Height: | Size: 921 B |
After Width: | Height: | Size: 711 B |
After Width: | Height: | Size: 887 B |
After Width: | Height: | Size: 938 B |
After Width: | Height: | Size: 978 B |
After Width: | Height: | Size: 956 B |
After Width: | Height: | Size: 752 B |
After Width: | Height: | Size: 811 B |
After Width: | Height: | Size: 805 B |
After Width: | Height: | Size: 790 B |
After Width: | Height: | Size: 817 B |
After Width: | Height: | Size: 837 B |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 1.0 KiB |
After Width: | Height: | Size: 1.0 KiB |
|
@ -0,0 +1,100 @@
|
|||
// Gem stone totem socket.
|
||||
|
||||
/*
|
||||
The Totem is a type of key-door that holds onto its corresponding
|
||||
Gemstone. When a doodad holding the right Gemstone touches the
|
||||
totem, the totem takes the gemstone and activates.
|
||||
|
||||
If the Totem is not linked to any other Totems, it immediately
|
||||
sends a power(true) signal upon activation.
|
||||
|
||||
If the Totem is linked to other Totems, it waits until all totems
|
||||
have been activated before it will emit a power signal. Only one
|
||||
such totem needs to be linked to e.g. an Electric Door - no matter
|
||||
which totem is solved last, they'll all emit a power signal when
|
||||
all of their linked totems are activated.
|
||||
*/
|
||||
|
||||
let color = Self.GetTag("color"),
|
||||
keyname = "gem-"+color+".doodad",
|
||||
activated = false,
|
||||
linkedReceiver = false, // is linked to a non-totem which might want power
|
||||
totems = {}, // linked totems
|
||||
shimmerFreq = 1000;
|
||||
|
||||
function main() {
|
||||
// Show the hollow socket on level load (last layer)
|
||||
Self.ShowLayer(4);
|
||||
|
||||
// Find any linked totems.
|
||||
for (let link of Self.GetLinks()) {
|
||||
if (link.Filename.indexOf("gem-totem") > -1) {
|
||||
totems[link.ID()] = false;
|
||||
} else {
|
||||
linkedReceiver = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Shimmer animation is just like the gemstones: first 4 frames
|
||||
// are the filled socket sprites.
|
||||
Self.AddAnimation("shimmer", 100, [0, 1, 2, 3, 0]);
|
||||
|
||||
Events.OnCollide((e) => {
|
||||
if (activated) return;
|
||||
|
||||
if (e.Actor.IsMobile() && e.Settled) {
|
||||
// Do they have our gemstone?
|
||||
let hasKey = e.Actor.HasItem(keyname) >= 0;
|
||||
if (!hasKey) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Take the gemstone.
|
||||
e.Actor.RemoveItem(keyname, 1);
|
||||
Self.ShowLayer(0);
|
||||
|
||||
// Emit to our linked totem neighbors.
|
||||
activated = true;
|
||||
Message.Publish("gem-totem:activated", Self.ID());
|
||||
tryPower();
|
||||
}
|
||||
});
|
||||
|
||||
Message.Subscribe("gem-totem:activated", (totemId) => {
|
||||
totems[totemId] = true;
|
||||
tryPower();
|
||||
})
|
||||
|
||||
setInterval(() => {
|
||||
if (activated) {
|
||||
Self.PlayAnimation("shimmer", null);
|
||||
}
|
||||
}, shimmerFreq);
|
||||
}
|
||||
|
||||
// Try to send a power signal for an activated totem.
|
||||
function tryPower() {
|
||||
// Only emit power if we are linked to something other than a totem.
|
||||
if (!linkedReceiver) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Can't if any of our linked totems aren't activated.
|
||||
try {
|
||||
for (let totemId of Object.keys(totems)) {
|
||||
if (totems[totemId] === false) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
} catch(e) {
|
||||
console.error("Caught: %s", e);
|
||||
}
|
||||
|
||||
// Can't if we aren't powered.
|
||||
if (activated === false) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Emit power!
|
||||
Message.Publish("power", true);
|
||||
}
|
After Width: | Height: | Size: 971 B |
After Width: | Height: | Size: 921 B |
After Width: | Height: | Size: 898 B |
After Width: | Height: | Size: 879 B |
|
@ -0,0 +1,15 @@
|
|||
ALL: build
|
||||
|
||||
.PHONY: build
|
||||
build:
|
||||
doodad convert -t "Snake" left-1.png left-2.png left-3.png right-1.png right-2.png right-3.png \
|
||||
attack-left-1.png attack-left-2.png attack-left-3.png attack-right-1.png attack-right-2.png \
|
||||
attack-right-3.png snake.doodad
|
||||
doodad install-script snake.js snake.doodad
|
||||
|
||||
# Tag the category for these doodads
|
||||
for i in *.doodad; do\
|
||||
doodad edit-doodad --tag "category=creatures" $${i};\
|
||||
done
|
||||
|
||||
cp *.doodad ../../../assets/doodads/
|
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 935 B |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 916 B |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 1.1 KiB |
After Width: | Height: | Size: 1.1 KiB |
|
@ -0,0 +1,131 @@
|
|||
// Snake
|
||||
|
||||
/*
|
||||
A.I. Behaviors:
|
||||
|
||||
- Always turns to face the nearest player character
|
||||
- Jumps up when the player tries to jump over them,
|
||||
aiming to attack the player.
|
||||
*/
|
||||
|
||||
let direction = "left",
|
||||
jumpSpeed = 12,
|
||||
watchRadius = 300, // player nearby distance for snake to jump
|
||||
jumpCooldownStart = time.Now(),
|
||||
size = Self.Size();
|
||||
|
||||
const states = {
|
||||
idle: 0,
|
||||
attacking: 1,
|
||||
};
|
||||
let state = states.idle;
|
||||
|
||||
function main() {
|
||||
Self.SetMobile(true);
|
||||
Self.SetGravity(true);
|
||||
Self.SetHitbox(20, 0, 28, 58);
|
||||
Self.AddAnimation("idle-left", 100, ["left-1", "left-2", "left-3", "left-2"]);
|
||||
Self.AddAnimation("idle-right", 100, ["right-1", "right-2", "right-3", "right-2"]);
|
||||
Self.AddAnimation("attack-left", 100, ["attack-left-1", "attack-left-2", "attack-left-3"])
|
||||
Self.AddAnimation("attack-right", 100, ["attack-right-1", "attack-right-2", "attack-right-3"])
|
||||
|
||||
// Player Character controls?
|
||||
if (Self.IsPlayer()) {
|
||||
return player();
|
||||
}
|
||||
|
||||
Events.OnCollide((e) => {
|
||||
// The snake is deadly to the touch.
|
||||
if (e.Settled && e.Actor.IsPlayer() && e.InHitbox) {
|
||||
// Friendly to fellow snakes.
|
||||
if (e.Actor.Doodad().Filename.indexOf("snake") > -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
FailLevel("Watch out for snakes!");
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
setInterval(() => {
|
||||
// Find the player.
|
||||
let player = Actors.FindPlayer(),
|
||||
playerPoint = player.Position(),
|
||||
point = Self.Position(),
|
||||
delta = 0,
|
||||
nearby = 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 we are idle and the player is jumping nearby...
|
||||
if (state == states.idle && nearby && Self.Grounded()) {
|
||||
if (playerPoint.Y - point.Y+(size.H/2) < 20) {
|
||||
// Enter attack state.
|
||||
if (time.Since(jumpCooldownStart) > 500 * time.Millisecond) {
|
||||
state = states.attacking;
|
||||
Self.SetVelocity(Vector(0, -jumpSpeed));
|
||||
Self.StopAnimation();
|
||||
Self.PlayAnimation("attack-"+direction, null);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If we are attacking and gravity has claimed us back.
|
||||
if (state === states.attacking && Self.Grounded()) {
|
||||
state = states.idle;
|
||||
jumpCooldownStart = time.Now();
|
||||
Self.StopAnimation();
|
||||
}
|
||||
|
||||
// Ensure that the animations are always rolling.
|
||||
if (state === states.idle && !Self.IsAnimating()) {
|
||||
Self.PlayAnimation("idle-"+direction, null);
|
||||
}
|
||||
}, 100);
|
||||
}
|
||||
|
||||
// If under control of the player character.
|
||||
function player() {
|
||||
let jumping = false;
|
||||
|
||||
Events.OnKeypress((ev) => {
|
||||
Vx = 0;
|
||||
Vy = 0;
|
||||
|
||||
if (ev.Right) {
|
||||
direction = "right";
|
||||
} else if (ev.Left) {
|
||||
direction = "left";
|
||||
}
|
||||
|
||||
// Jump!
|
||||
if (ev.Up && !jumping) {
|
||||
Self.StopAnimation();
|
||||
Self.PlayAnimation("attack-"+direction, null);
|
||||
jumping = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (jumping && Self.Grounded()) {
|
||||
Self.StopAnimation();
|
||||
jumping = false;
|
||||
}
|
||||
|
||||
if (!jumping && !Self.IsAnimating()) {
|
||||
Self.PlayAnimation("idle-"+direction, null);
|
||||
}
|
||||
});
|
||||
}
|