2021-08-12 03:40:31 +00:00
|
|
|
// Azulian (Red and Blue)
|
2022-01-18 05:28:05 +00:00
|
|
|
|
|
|
|
const color = Self.GetTag("color");
|
|
|
|
var playerSpeed = color === 'blue' ? 2 : 4,
|
2022-05-06 04:35:32 +00:00
|
|
|
swimSpeed = playerSpeed * 0.4,
|
2022-01-18 05:28:05 +00:00
|
|
|
aggroX = 250, // X/Y distance sensitivity from player
|
|
|
|
aggroY = color === 'blue' ? 100 : 200,
|
2022-03-26 20:55:06 +00:00
|
|
|
jumpSpeed = color === 'blue' ? 14 : 18,
|
2022-05-06 04:35:32 +00:00
|
|
|
swimJumpSpeed = jumpSpeed * 0.4,
|
2021-08-12 03:40:31 +00:00
|
|
|
animating = false,
|
2021-09-04 02:54:10 +00:00
|
|
|
direction = "right",
|
|
|
|
lastDirection = "right";
|
2021-08-12 03:40:31 +00:00
|
|
|
|
2022-01-19 02:32:15 +00:00
|
|
|
// white Azulian is faster yet than the red
|
|
|
|
if (color === 'white') {
|
|
|
|
aggroX = 1000;
|
|
|
|
aggroY = 400;
|
|
|
|
playerSpeed = 8;
|
2022-05-06 04:35:32 +00:00
|
|
|
swimSpeed = playerSpeed * 0.4;
|
2022-03-26 20:55:06 +00:00
|
|
|
jumpSpeed = 20;
|
2022-05-06 04:35:32 +00:00
|
|
|
swimJumpSpeed = jumpSpeed * 0.4;
|
2022-01-19 02:32:15 +00:00
|
|
|
}
|
|
|
|
|
2021-08-12 03:40:31 +00:00
|
|
|
function setupAnimations(color) {
|
2022-03-07 06:16:09 +00:00
|
|
|
let left = color === 'blue' ? 'blu-wl' : color + '-wl',
|
|
|
|
right = color === 'blue' ? 'blu-wr' : color + '-wr',
|
2021-08-12 03:40:31 +00:00
|
|
|
leftFrames = [left + '1', left + '2', left + '3', left + '4'],
|
|
|
|
rightFrames = [right + '1', right + '2', right + '3', right + '4'];
|
2019-05-05 21:03:20 +00:00
|
|
|
|
2021-08-12 03:40:31 +00:00
|
|
|
Self.AddAnimation("walk-left", 100, leftFrames);
|
|
|
|
Self.AddAnimation("walk-right", 100, rightFrames);
|
|
|
|
}
|
|
|
|
|
|
|
|
function main() {
|
|
|
|
playerSpeed = color === 'blue' ? 2 : 4;
|
2019-05-05 21:03:20 +00:00
|
|
|
|
2022-05-06 04:35:32 +00:00
|
|
|
let swimJumpCooldownTick = 0, // minimum Game Tick before we can jump while swimming
|
|
|
|
swimJumpCooldown = 10; // CONFIG: delta of ticks between jumps while swimming
|
|
|
|
|
2019-12-31 02:13:28 +00:00
|
|
|
Self.SetMobile(true);
|
2019-05-06 02:04:02 +00:00
|
|
|
Self.SetGravity(true);
|
Thief and Inventory APIs
This commit adds the Thief character with starter graphics
(no animations).
The Thief walks back and forth and will steal items from other
doodads, including the player. For singleton items that have no
quantity, like the Colored Keys, the Thief will only steal one
if he does not already have it. Quantitied items like the
Small Key are always stolen.
Flexibility in the playable character is introduced: Boy,
Azulian, Bird, and Thief all respond to playable controls.
There is not currently a method to enable these apart from
modifying balance.PlayerCharacterDoodad at compile time.
New and Changed Doodads
* Thief: new doodad that walks back and forth and will steal
items from other characters inventory.
* Bird: has no inventory and cannot pick up items, unless player
controlled. Its hitbox has also been fixed so it collides with
floors correctly - not something normally seen in the Bird.
* Boy: opts in to have inventory.
* Keys (all): only gives themselves to actors having inventories.
JavaScript API - New functions available
* Self.IsPlayer() - returns if the current actor IS the player.
* Self.SetInventory(bool) - doodads must opt-in to having an
inventory. Keys should only give themselves to doodads having
an inventory.
* Self.HasInventory() bool
* Self.AddItem(filename, qty)
* Self.RemoveItem(filename, qty)
* Self.HasItem(filename)
* Self.Inventory() - returns map[string]int
* Self.ClearInventory()
* Self.OnLeave(func(e)) now receives a CollideEvent as parameter
instead of the useless actor ID. Notably, e.Actor is the
leaving actor and e.Settled is always true.
Other Changes
* Play Mode: if playing as a character which doesn't obey gravity,
such as the bird, antigravity controls are enabled by default.
If you `import antigravity` you can turn gravity back on.
* Doodad collision scripts are no longer run in parallel
goroutines. It made the Thief's job difficult trying to steal
items in many threads simultaneously!
2021-08-10 05:42:22 +00:00
|
|
|
Self.SetInventory(true);
|
2021-08-12 03:40:31 +00:00
|
|
|
Self.SetHitbox(0, 0, 24, 32);
|
|
|
|
setupAnimations(color);
|
2019-05-05 21:03:20 +00:00
|
|
|
|
2021-08-12 03:40:31 +00:00
|
|
|
if (Self.IsPlayer()) {
|
|
|
|
return playerControls();
|
|
|
|
}
|
|
|
|
|
|
|
|
// A.I. pattern: walks back and forth, turning around
|
|
|
|
// when it meets resistance.
|
2019-05-05 21:03:20 +00:00
|
|
|
|
2021-08-12 03:40:31 +00:00
|
|
|
// Sample our X position every few frames and detect if we've hit a solid wall.
|
2022-01-17 04:09:27 +00:00
|
|
|
let sampleTick = 0;
|
|
|
|
let sampleRate = 5;
|
|
|
|
let lastSampledX = 0;
|
2021-08-12 03:40:31 +00:00
|
|
|
|
2022-01-18 05:28:05 +00:00
|
|
|
// 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!
|
2022-02-20 20:19:56 +00:00
|
|
|
if (e.Settled && isPlayerFood(e.Actor)) {
|
2022-01-18 05:28:05 +00:00
|
|
|
FailLevel("Watch out for the Azulians!");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2022-01-17 04:09:27 +00:00
|
|
|
setInterval(() => {
|
2022-01-18 05:28:05 +00:00
|
|
|
// 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;
|
2022-02-20 20:19:56 +00:00
|
|
|
|
|
|
|
// Don't follow boring players.
|
|
|
|
if (player !== null && isPlayerFood(player)) {
|
2022-01-18 05:28:05 +00:00
|
|
|
let playerPt = player.Position(),
|
|
|
|
myPt = Self.Position();
|
|
|
|
|
|
|
|
// If the player is within aggro range, move towards.
|
2022-03-07 06:16:09 +00:00
|
|
|
if ((Math.abs(playerPt.X - myPt.X) < aggroX && Math.abs(playerPt.Y - myPt.Y) < aggroY)
|
|
|
|
|| (Level.Difficulty > 0)) {
|
2022-01-18 05:28:05 +00:00
|
|
|
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;
|
2021-08-12 03:40:31 +00:00
|
|
|
}
|
2022-01-18 05:28:05 +00:00
|
|
|
sampleTick++;
|
2021-08-12 03:40:31 +00:00
|
|
|
}
|
|
|
|
|
2022-05-06 04:35:32 +00:00
|
|
|
// 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;
|
2022-01-18 05:28:05 +00:00
|
|
|
Self.SetVelocity(Vector(Vx, Vy));
|
2021-08-12 03:40:31 +00:00
|
|
|
|
2021-09-04 02:54:10 +00:00
|
|
|
// If we changed directions, stop animating now so we can
|
|
|
|
// turn around quickly without moonwalking.
|
|
|
|
if (direction !== lastDirection) {
|
|
|
|
Self.StopAnimation();
|
|
|
|
}
|
|
|
|
|
2021-08-12 03:40:31 +00:00
|
|
|
if (!Self.IsAnimating()) {
|
|
|
|
Self.PlayAnimation("walk-" + direction, null);
|
|
|
|
}
|
2021-09-04 02:54:10 +00:00
|
|
|
|
|
|
|
lastDirection = direction;
|
2022-01-18 05:28:05 +00:00
|
|
|
}, 10);
|
2021-08-12 03:40:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
function playerControls() {
|
|
|
|
// Note: player speed is controlled by the engine.
|
2022-01-17 04:09:27 +00:00
|
|
|
Events.OnKeypress((ev) => {
|
2019-12-23 02:21:58 +00:00
|
|
|
if (ev.Right) {
|
2019-05-06 02:04:02 +00:00
|
|
|
if (!Self.IsAnimating()) {
|
|
|
|
Self.PlayAnimation("walk-right", null);
|
|
|
|
}
|
2019-12-23 02:21:58 +00:00
|
|
|
} else if (ev.Left) {
|
2019-05-06 02:04:02 +00:00
|
|
|
if (!Self.IsAnimating()) {
|
|
|
|
Self.PlayAnimation("walk-left", null);
|
|
|
|
}
|
2019-05-05 21:03:20 +00:00
|
|
|
} else {
|
2019-05-06 02:04:02 +00:00
|
|
|
Self.StopAnimation();
|
2019-05-05 21:03:20 +00:00
|
|
|
animating = false;
|
|
|
|
}
|
|
|
|
})
|
2019-05-02 01:27:20 +00:00
|
|
|
}
|
2022-02-20 20:19:56 +00:00
|
|
|
|
|
|
|
// 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) {
|
2022-03-07 06:16:09 +00:00
|
|
|
// Not a player or is invulnerable, or Peaceful difficulty.
|
|
|
|
if (!actor.IsPlayer() || actor.Invulnerable() || Level.Difficulty < 0) {
|
2022-02-20 20:19:56 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2022-03-07 06:16:09 +00:00
|
|
|
// On hard mode they are hostile to any player.
|
|
|
|
if (Level.Difficulty > 0) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2022-02-20 20:19:56 +00:00
|
|
|
// Azulians are friendly to Thieves and other Azulians.
|
|
|
|
if (actor.Doodad().Filename === "thief.doodad" || actor.Doodad().Title.indexOf("Azulian") > -1) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|