Rewrite movement loop and timing to better match Lynx rules
This commit is contained in:
parent
413c511fe1
commit
731d3c15a8
149
js/main.js
149
js/main.js
@ -102,6 +102,7 @@ class Tile {
|
||||
this.direction = direction;
|
||||
|
||||
this.slide_mode = null;
|
||||
this.movement_cooldown = 0;
|
||||
|
||||
if (type.has_inventory) {
|
||||
this.inventory = {};
|
||||
@ -279,98 +280,108 @@ class Level {
|
||||
}
|
||||
}
|
||||
|
||||
halftic() {
|
||||
advance_tic(player_direction) {
|
||||
if (this.state !== 'playing') {
|
||||
console.warn(`Level.halftic() called when state is ${this.state}`);
|
||||
return;
|
||||
}
|
||||
|
||||
for (let actor of this.actors) {
|
||||
if (actor.slide_mode !== null) {
|
||||
// TODO do we stop sliding if we hit something, too?
|
||||
this.attempt_step(actor, actor.direction);
|
||||
}
|
||||
|
||||
if (this.state === 'success' || this.state === 'failure')
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
advance(player_direction) {
|
||||
if (this.state !== 'playing') {
|
||||
console.warn(`Level.advance() called when state is ${this.state}`);
|
||||
console.warn(`Level.advance_tic() called when state is ${this.state}`);
|
||||
return;
|
||||
}
|
||||
|
||||
// XXX this entire turn order is rather different in ms rules
|
||||
for (let actor of this.actors) {
|
||||
// TODO strip these out maybe??
|
||||
if (actor.doomed)
|
||||
continue;
|
||||
|
||||
// Actors can't make voluntary moves on ice
|
||||
if (actor.slide_mode === 'ice')
|
||||
continue;
|
||||
if (actor === this.player) {
|
||||
if (actor.movement_cooldown > 0) {
|
||||
actor.movement_cooldown -= 1;
|
||||
if (actor.movement_cooldown > 0)
|
||||
continue;
|
||||
}
|
||||
|
||||
let direction_preference;
|
||||
// Actors can't make voluntary moves on ice, so they're stuck with
|
||||
// whatever they've got
|
||||
if (actor.slide_mode === 'ice') {
|
||||
direction_preference = [actor.direction];
|
||||
}
|
||||
else if (actor.slide_mode === 'force') {
|
||||
// Only the player can make voluntary moves on a force floor,
|
||||
// and only if their previous move was an /involuntary/ move on
|
||||
// a force floor. If they do, it overrides the forced move
|
||||
// XXX this in particular has some subtleties in lynx (e.g. you
|
||||
// can override forwards??) and DEFINITELY all kinds of stuff
|
||||
// in ms
|
||||
if (actor === this.player &&
|
||||
player_direction &&
|
||||
actor.last_move_was_force)
|
||||
{
|
||||
direction_preference = [player_direction];
|
||||
actor.last_move_was_force = false;
|
||||
}
|
||||
else {
|
||||
direction_preference = [actor.direction];
|
||||
if (actor === this.player) {
|
||||
actor.last_move_was_force = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (actor === this.player) {
|
||||
if (player_direction) {
|
||||
actor.direction = player_direction;
|
||||
this.attempt_step(actor, player_direction);
|
||||
direction_preference = [player_direction];
|
||||
actor.last_move_was_force = false;
|
||||
}
|
||||
}
|
||||
else if (actor.type.movement_mode === 'follow-left') {
|
||||
// bug behavior: always try turning as left as possible, and
|
||||
// fall back to less-left turns when that fails
|
||||
let direction = DIRECTIONS[actor.direction].left;
|
||||
for (let i = 0; i < 4; i++) {
|
||||
if (this.attempt_step(actor, direction)) {
|
||||
actor.direction = direction;
|
||||
break;
|
||||
}
|
||||
direction = DIRECTIONS[direction].right;
|
||||
}
|
||||
let d = DIRECTIONS[actor.direction];
|
||||
direction_preference = [d.left, actor.direction, d.right, d.opposite];
|
||||
}
|
||||
else if (actor.type.movement_mode === 'follow-right') {
|
||||
// paramecium behavior: always try turning as right as
|
||||
// possible, and fall back to less-right turns when that fails
|
||||
let direction = DIRECTIONS[actor.direction].right;
|
||||
for (let i = 0; i < 4; i++) {
|
||||
if (this.attempt_step(actor, direction)) {
|
||||
actor.direction = direction;
|
||||
break;
|
||||
}
|
||||
direction = DIRECTIONS[direction].left;
|
||||
}
|
||||
let d = DIRECTIONS[actor.direction];
|
||||
direction_preference = [d.right, actor.direction, d.left, d.opposite];
|
||||
}
|
||||
else if (actor.type.movement_mode === 'turn-left') {
|
||||
// glider behavior: preserve current direction; if that doesn't
|
||||
// work, turn left, then right, then back the way we came
|
||||
for (let direction of [
|
||||
actor.direction,
|
||||
DIRECTIONS[actor.direction].left,
|
||||
DIRECTIONS[actor.direction].right,
|
||||
DIRECTIONS[actor.direction].opposite,
|
||||
]) {
|
||||
if (this.attempt_step(actor, direction)) {
|
||||
actor.direction = direction;
|
||||
break;
|
||||
}
|
||||
}
|
||||
let d = DIRECTIONS[actor.direction];
|
||||
direction_preference = [actor.direction, d.left, d.right, d.opposite];
|
||||
}
|
||||
else if (actor.type.movement_mode === 'turn-right') {
|
||||
// fireball behavior: preserve current direction; if that doesn't
|
||||
// work, turn right, then left, then back the way we came
|
||||
for (let direction of [
|
||||
actor.direction,
|
||||
DIRECTIONS[actor.direction].right,
|
||||
DIRECTIONS[actor.direction].left,
|
||||
DIRECTIONS[actor.direction].opposite,
|
||||
]) {
|
||||
if (this.attempt_step(actor, direction)) {
|
||||
actor.direction = direction;
|
||||
break;
|
||||
}
|
||||
let d = DIRECTIONS[actor.direction];
|
||||
direction_preference = [actor.direction, d.right, d.left, d.opposite];
|
||||
}
|
||||
else if (actor.type.movement_mode === 'bounce') {
|
||||
// bouncy ball behavior: preserve current direction; if that
|
||||
// doesn't work, bounce back the way we came
|
||||
let d = DIRECTIONS[actor.direction];
|
||||
direction_preference = [actor.direction, d.opposite];
|
||||
}
|
||||
|
||||
if (! direction_preference)
|
||||
continue;
|
||||
|
||||
let moved = false;
|
||||
for (let direction of direction_preference) {
|
||||
actor.direction = direction;
|
||||
if (this.attempt_step(actor, direction)) {
|
||||
moved = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Always set the cooldown if we even attempt to move. Speed
|
||||
// multiplier is based on the tile we landed /on/, if any.
|
||||
let speed_multiplier = 1;
|
||||
if (actor.slide_mode !== null) {
|
||||
speed_multiplier = 2;
|
||||
}
|
||||
actor.movement_cooldown = actor.type.movement_speed / speed_multiplier;
|
||||
|
||||
// TODO do i need to do this more aggressively?
|
||||
if (this.state === 'success' || this.state === 'failure')
|
||||
break;
|
||||
@ -382,6 +393,8 @@ class Level {
|
||||
this.fail_message = message;
|
||||
}
|
||||
|
||||
// Try to move the given actor one tile in the given direction and update
|
||||
// their cooldown. Return true if successful.
|
||||
attempt_step(actor, direction) {
|
||||
let move = DIRECTIONS[direction].movement;
|
||||
let goal_x = actor.x + move[0];
|
||||
@ -442,6 +455,9 @@ class Level {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Move the given actor to the given position and perform any appropriate
|
||||
// tile interactions. Does NOT check for whether the move is actually
|
||||
// legal; use attempt_step for that!
|
||||
move_to(actor, x, y) {
|
||||
if (x === actor.x && y === actor.y)
|
||||
return;
|
||||
@ -644,15 +660,10 @@ class Game {
|
||||
do_frame() {
|
||||
if (this.level.state === 'playing') {
|
||||
this.frame++;
|
||||
if (this.frame % 6 === 0) {
|
||||
this.level.halftic();
|
||||
}
|
||||
if (this.frame % 12 === 0) {
|
||||
this.level.advance(this.next_player_move);
|
||||
if (this.frame % 3 === 0) {
|
||||
this.level.advance_tic(this.next_player_move);
|
||||
this.next_player_move = this.pending_player_move;
|
||||
this.player_used_move = true;
|
||||
}
|
||||
if (this.frame % 6 === 0) {
|
||||
this.redraw();
|
||||
}
|
||||
this.frame %= 60;
|
||||
|
||||
@ -197,25 +197,25 @@ const TILE_TYPES = {
|
||||
force_floor_n: {
|
||||
on_arrive(me, level, other) {
|
||||
other.direction = 'north';
|
||||
level.make_slide(other, 'push');
|
||||
level.make_slide(other, 'force');
|
||||
}
|
||||
},
|
||||
force_floor_e: {
|
||||
on_arrive(me, level, other) {
|
||||
other.direction = 'east';
|
||||
level.make_slide(other, 'push');
|
||||
level.make_slide(other, 'force');
|
||||
}
|
||||
},
|
||||
force_floor_s: {
|
||||
on_arrive(me, level, other) {
|
||||
other.direction = 'south';
|
||||
level.make_slide(other, 'push');
|
||||
level.make_slide(other, 'force');
|
||||
}
|
||||
},
|
||||
force_floor_w: {
|
||||
on_arrive(me, level, other) {
|
||||
other.direction = 'west';
|
||||
level.make_slide(other, 'push');
|
||||
level.make_slide(other, 'force');
|
||||
}
|
||||
},
|
||||
bomb: {
|
||||
@ -261,33 +261,40 @@ const TILE_TYPES = {
|
||||
is_object: true,
|
||||
is_monster: true,
|
||||
movement_mode: 'follow-left',
|
||||
movement_speed: 4,
|
||||
},
|
||||
paramecium: {
|
||||
is_actor: true,
|
||||
is_object: true,
|
||||
is_monster: true,
|
||||
movement_mode: 'follow-right',
|
||||
movement_speed: 4,
|
||||
},
|
||||
ball: {
|
||||
is_actor: true,
|
||||
is_object: true,
|
||||
is_monster: true,
|
||||
movement_mode: 'bounce',
|
||||
movement_speed: 4,
|
||||
},
|
||||
blob: {
|
||||
is_actor: true,
|
||||
is_object: true,
|
||||
is_monster: true,
|
||||
movement_speed: 8,
|
||||
},
|
||||
teeth: {
|
||||
is_actor: true,
|
||||
is_object: true,
|
||||
is_monster: true,
|
||||
movement_speed: 4,
|
||||
},
|
||||
fireball: {
|
||||
is_actor: true,
|
||||
is_object: true,
|
||||
is_monster: true,
|
||||
movement_mode: 'turn-right',
|
||||
movement_speed: 4,
|
||||
ignores: new Set(['fire']),
|
||||
},
|
||||
glider: {
|
||||
@ -295,6 +302,7 @@ const TILE_TYPES = {
|
||||
is_object: true,
|
||||
is_monster: true,
|
||||
movement_mode: 'turn-left',
|
||||
movement_speed: 4,
|
||||
ignores: new Set(['water']),
|
||||
},
|
||||
|
||||
@ -356,9 +364,11 @@ const TILE_TYPES = {
|
||||
is_player: true,
|
||||
has_inventory: true,
|
||||
is_object: true,
|
||||
movement_speed: 4,
|
||||
pushes: {
|
||||
dirt_block: true,
|
||||
},
|
||||
// FIXME this prevents thief from taking green key
|
||||
infinite_items: {
|
||||
key_green: true,
|
||||
},
|
||||
|
||||
Loading…
Reference in New Issue
Block a user