Update Doodad JS API + Hostile mobs

New functions are available on the JavaScript API for doodads:

* Actors.At(Point) []*Actor: returns actors intersecting a point
* Actors.FindPlayer() *Actor: returns the nearest player character
* Actors.New(filename string): create a new actor (NOT TESTED YET!)
* Self.Grounded() bool: query the grounded status of current actor

With this the game's built-in doodads have been revised:

* Bird: will now scan 240 pixels diagonally searching for the player
  character and will dive if seen. The Bird is dangerous while
  diving. It will return to its original altitude once it touches
  the ground.
* Azulians: the Azulians are now dangerous to player characters but
  not to the Thief. Azulians will begin to follow the player when
  they are within the aggro range and will hop if the player is
  above them to try and overcome obstacles.
  * Blue Azulian: aggro is (250, 100) jump speed 12 movement 2
  * Red Azulian: aggro is (250, 200) jump speed 14 movement 4
master
Noah 2022-01-17 21:28:05 -08:00
parent c1a87e03e6
commit 2913e706e2
2 changed files with 131 additions and 26 deletions

View File

@ -1,5 +1,10 @@
// Azulian (Red and Blue)
var playerSpeed = 12,
const color = Self.GetTag("color");
var playerSpeed = color === 'blue' ? 2 : 4,
aggroX = 250, // X/Y distance sensitivity from player
aggroY = color === 'blue' ? 100 : 200,
jumpSpeed = color === 'blue' ? 12 : 14,
animating = false,
direction = "right",
lastDirection = "right";
@ -15,7 +20,6 @@ function setupAnimations(color) {
}
function main() {
const color = Self.GetTag("color");
playerSpeed = color === 'blue' ? 2 : 4;
Self.SetMobile(true);
@ -36,19 +40,53 @@ function main() {
let sampleRate = 5;
let lastSampledX = 0;
setInterval(() => {
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;
// 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 && e.Actor.IsPlayer() && e.Actor.Doodad().Filename !== "thief.doodad") {
FailLevel("Watch out for the Azulians!");
return;
}
sampleTick++;
});
let Vx = parseFloat(playerSpeed * (direction === "left" ? -1 : 1));
Self.SetVelocity(Vector(Vx, 0.0));
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;
if (player !== null) {
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) {
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++;
}
let Vx = parseFloat(playerSpeed * (direction === "left" ? -1 : 1)),
Vy = jump && Self.Grounded() ? parseFloat(-jumpSpeed) : Self.GetVelocity().Y;
Self.SetVelocity(Vector(Vx, Vy));
// If we changed directions, stop animating now so we can
// turn around quickly without moonwalking.
@ -61,7 +99,7 @@ function main() {
}
lastDirection = direction;
}, 100);
}, 10);
}
function playerControls() {

View File

@ -1,7 +1,6 @@
// Bird
function main() {
let speed = 4,
let speed = 4,
Vx = Vy = 0,
altitude = Self.Position().Y; // original height in the level
@ -13,6 +12,7 @@ function main() {
};
let state = states.flying;
function main() {
Self.SetMobile(true);
Self.SetGravity(false);
Self.SetHitbox(0, 0, 46, 32);
@ -25,6 +25,12 @@ function main() {
}
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.InHitbox) {
return false;
}
@ -33,25 +39,42 @@ function main() {
// Sample our X position every few frames and detect if we've hit a solid wall.
let sampleTick = 0,
sampleRate = 2,
lastSampledX = 0,
lastSampledY = 0;
lastSampled = Point(0, 0);
setInterval(() => {
// Sample how far we've moved to detect hitting a wall.
if (sampleTick % sampleRate === 0) {
let curX = Self.Position().X;
let delta = Math.abs(curX - lastSampledX);
let curP = Self.Position()
let delta = Math.abs(curP.X - lastSampled.X);
if (delta < 5) {
direction = direction === "right" ? "left" : "right";
}
lastSampledX = curX;
// 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++;
// If we are not flying at our original altitude, correct for that.
let curV = Self.Position();
let Vy = 0.0;
if (curV.Y != altitude) {
Vy = curV.Y < altitude ? 1 : -1;
// 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 != altitude) {
Vy = curV.Y < altitude ? 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,
@ -59,6 +82,13 @@ function main() {
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) {
@ -73,6 +103,43 @@ function main() {
}, 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.
function AI_ScanForPlayer() {
let stepY = 12, // number of pixels to skip
stepX = stepY,
limit = stepX * 20, // 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()) {
state = states.diving;
return;
}
}
}
return;
}
// If under control of the player character.
function player() {
Self.SetInventory(true);