Add teleport overriding and seriously clean up teleport code
This commit is contained in:
parent
7c82a4cdf9
commit
25b4b32f94
142
js/game.js
142
js/game.js
@ -631,7 +631,7 @@ export class Level {
|
||||
continue;
|
||||
|
||||
if (actor.just_stepped_on_teleporter) {
|
||||
this.attempt_teleport(actor);
|
||||
this.attempt_teleport(actor, actor === this.player ? p1_input : null);
|
||||
}
|
||||
}
|
||||
|
||||
@ -678,6 +678,11 @@ export class Level {
|
||||
continue;
|
||||
|
||||
// Check for special player actions, which can only happen when not moving
|
||||
// FIXME if you press a key while moving it should happen as soon as you stop (assuming
|
||||
// the key is still held down)
|
||||
// FIXME cc2 seems to rely on key repeat for this; the above is true, but also, if you
|
||||
// have four bowling balls and hold Q, you'll throw the first, wait a second or so, then
|
||||
// release the rest rapid-fire. absurd
|
||||
if (actor === this.player) {
|
||||
let new_input = p1_input & ~this.previous_input;
|
||||
if (new_input & INPUT_BITS.cycle) {
|
||||
@ -768,13 +773,8 @@ export class Level {
|
||||
this.commit();
|
||||
}
|
||||
|
||||
make_player_decision(actor, input) {
|
||||
// Only reset the player's is_pushing between movement, so it lasts for the whole push
|
||||
this._set_tile_prop(actor, 'is_pushing', false);
|
||||
|
||||
// TODO player in a cloner can't move (but player in a trap can still turn)
|
||||
|
||||
// Extract directions from the input mask
|
||||
_extract_player_directions(input) {
|
||||
// Extract directions from an input mask
|
||||
let dir1 = null, dir2 = null;
|
||||
if (((input & INPUT_BITS['up']) && (input & INPUT_BITS['down'])) ||
|
||||
((input & INPUT_BITS['left']) && (input & INPUT_BITS['right'])))
|
||||
@ -795,15 +795,21 @@ export class Level {
|
||||
}
|
||||
}
|
||||
}
|
||||
return [dir1, dir2];
|
||||
}
|
||||
|
||||
make_player_decision(actor, input) {
|
||||
// Only reset the player's is_pushing between movement, so it lasts for the whole push
|
||||
this._set_tile_prop(actor, 'is_pushing', false);
|
||||
|
||||
// TODO player in a cloner can't move (but player in a trap can still turn)
|
||||
|
||||
let [dir1, dir2] = this._extract_player_directions(input);
|
||||
let try_direction = (direction, push_mode) => {
|
||||
direction = actor.cell.redirect_exit(actor, direction);
|
||||
let dest_cell = this.get_neighboring_cell(actor.cell, direction);
|
||||
return (dest_cell &&
|
||||
! actor.cell.blocks_leaving(actor, direction) &&
|
||||
// FIXME if the player steps into a monster cell here, they die instantly! but only
|
||||
// if the cell doesn't block them??
|
||||
! dest_cell.blocks_entering(actor, direction, this, push_mode));
|
||||
// FIXME if the player steps into a monster cell here, they die instantly! but only
|
||||
// if the cell doesn't block them??
|
||||
return this.check_movement(actor, actor.cell, direction, push_mode);
|
||||
};
|
||||
|
||||
// The player is unusual in several ways.
|
||||
@ -957,7 +963,7 @@ export class Level {
|
||||
|
||||
direction = actor.cell.redirect_exit(actor, direction);
|
||||
|
||||
if (this.is_move_allowed(actor, direction, 'trace')) {
|
||||
if (this.check_movement(actor, actor.cell, direction, 'trace')) {
|
||||
// We found a good direction! Stop here
|
||||
actor.decision = direction;
|
||||
all_blocked = false;
|
||||
@ -999,10 +1005,10 @@ export class Level {
|
||||
}
|
||||
|
||||
// FIXME make this interact with railroads correctly and use it for players and in attempt_step
|
||||
is_move_allowed(actor, direction, push_mode) {
|
||||
let dest_cell = this.get_neighboring_cell(actor.cell, direction);
|
||||
check_movement(actor, orig_cell, direction, push_mode) {
|
||||
let dest_cell = this.get_neighboring_cell(orig_cell, direction);
|
||||
return (dest_cell &&
|
||||
! actor.cell.blocks_leaving(actor, direction) &&
|
||||
! orig_cell.blocks_leaving(actor, direction) &&
|
||||
! dest_cell.blocks_entering(actor, direction, this, push_mode));
|
||||
}
|
||||
|
||||
@ -1263,70 +1269,82 @@ export class Level {
|
||||
}
|
||||
}
|
||||
|
||||
attempt_teleport(actor) {
|
||||
attempt_teleport(actor, input) {
|
||||
let teleporter = actor.just_stepped_on_teleporter;
|
||||
actor.just_stepped_on_teleporter = null;
|
||||
|
||||
// Handle teleporting, now that the dust has cleared
|
||||
// FIXME something funny happening here, your input isn't ignored while walking out of it?
|
||||
let push_mode = actor === this.player ? 'move' : 'trace';
|
||||
let original_direction = actor.direction;
|
||||
let success = false;
|
||||
for (let dest of teleporter.type.teleport_dest_order(teleporter, this, actor)) {
|
||||
let dest, direction;
|
||||
for ([dest, direction] of teleporter.type.teleport_dest_order(teleporter, this, actor)) {
|
||||
// Teleporters already containing an actor are blocked and unusable
|
||||
// FIXME should check collision?
|
||||
if (dest.cell.some(tile => tile.type.is_actor && tile !== actor))
|
||||
continue;
|
||||
|
||||
// Physically move the actor to the new teleporter
|
||||
// XXX lynx treats this as a slide and does it in a pass in the main loop
|
||||
// XXX not especially undo-efficient
|
||||
// FIXME the new aggressive move checker could help here!
|
||||
this.remove_tile(actor);
|
||||
this.add_tile(actor, dest.cell);
|
||||
|
||||
// FIXME teleport overrides... also allow block slapping... seems like horizontal
|
||||
// wins...
|
||||
|
||||
// Red and green teleporters attempt to spit you out in every direction before
|
||||
// giving up on a destination (but not if you return to the original).
|
||||
// Note that we use actor.direction here (rather than original_direction) because
|
||||
// green teleporters modify it in teleport_dest_order, to randomize the exit
|
||||
// direction
|
||||
let direction = actor.direction;
|
||||
let num_directions = 1;
|
||||
if (teleporter.type.teleport_try_all_directions && dest !== teleporter) {
|
||||
num_directions = 4;
|
||||
}
|
||||
// FIXME bleugh hardcode
|
||||
if (dest === teleporter && teleporter.type.name === 'teleport_yellow') {
|
||||
break;
|
||||
}
|
||||
for (let i = 0; i < num_directions; i++) {
|
||||
if (this.attempt_out_of_turn_step(actor, direction)) {
|
||||
success = true;
|
||||
// Sound plays from the origin cell simply because that's where the sfx player
|
||||
// thinks the player is currently; position isn't updated til next turn
|
||||
this.sfx.play_once('teleport', teleporter.cell);
|
||||
break;
|
||||
}
|
||||
else {
|
||||
direction = DIRECTIONS[direction].right;
|
||||
}
|
||||
}
|
||||
|
||||
if (success) {
|
||||
if (this.check_movement(actor, dest.cell, direction, push_mode)) {
|
||||
success = true;
|
||||
// Sound plays from the origin cell simply because that's where the sfx player
|
||||
// thinks the player is currently; position isn't updated til next turn
|
||||
this.sfx.play_once('teleport', teleporter.cell);
|
||||
break;
|
||||
}
|
||||
else if (num_directions === 4) {
|
||||
// Restore our original facing before continuing
|
||||
// (For red teleports, we try every possible destination in our original
|
||||
// movement direction, so this is correct. For green teleports, we only try one
|
||||
// destination and then fall back to walking through the source in our original
|
||||
// movement direction, so this is still correct.)
|
||||
this.set_actor_direction(actor, original_direction);
|
||||
}
|
||||
}
|
||||
|
||||
if (success) {
|
||||
if (teleporter.type.teleport_allow_override && actor === this.player) {
|
||||
// Red and yellow teleporters allow players to override the exit direction. This
|
||||
// can only happen after we've found a suitable destination. As with normal player
|
||||
// decisions, we aggressively check each direction first (meaning we might bump the
|
||||
// same cell twice here!), and then figure out what to do afterwards.
|
||||
// Note that it's possible to bump a direction multiple times during this process,
|
||||
// and also possible to perform a three-way block slap: the direction she leaves,
|
||||
// the other direction she was holding, and the original exit direction we found.
|
||||
let [dir1, dir2] = this._extract_player_directions(input);
|
||||
let open1 = false, open2 = false;
|
||||
if (dir1) {
|
||||
open1 = this.check_movement(actor, dest.cell, dir1, push_mode);
|
||||
}
|
||||
if (dir2) {
|
||||
open2 = this.check_movement(actor, dest.cell, dir2, push_mode);
|
||||
}
|
||||
|
||||
// If the player didn't even try to override, do nothing
|
||||
if (! dir1 && ! dir2) {
|
||||
}
|
||||
// If only one direction is available, whether because she only held one direction
|
||||
// or because one of them was blocked, use that one
|
||||
else if ((open1 && ! open2) || (dir1 && ! dir2)) {
|
||||
direction = dir1;
|
||||
}
|
||||
else if (! open1 && open2) {
|
||||
direction = dir2;
|
||||
}
|
||||
// Otherwise, we have a tie. If either direction is the exit we found (which
|
||||
// can only happen if both are open), prefer that one...
|
||||
else if (dir1 === direction || dir2 === direction) {
|
||||
}
|
||||
// ...otherwise, prefer the horizontal one.
|
||||
else if (dir1 === 'west' || dir1 === 'east') {
|
||||
direction = dir1;
|
||||
}
|
||||
else {
|
||||
direction = dir2;
|
||||
}
|
||||
}
|
||||
|
||||
// Now physically move the actor and have them take a turn
|
||||
this.remove_tile(actor);
|
||||
this.add_tile(actor, dest.cell);
|
||||
this.attempt_out_of_turn_step(actor, direction);
|
||||
}
|
||||
if (! success && actor.type.has_inventory && teleporter.type.name === 'teleport_yellow') {
|
||||
// Super duper special yellow teleporter behavior: you pick it the fuck up
|
||||
// FIXME not if there's only one in the level?
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { COLLISION, DIRECTIONS, DRAW_LAYERS } from './defs.js';
|
||||
import { COLLISION, DIRECTIONS, DIRECTION_ORDER, DRAW_LAYERS } from './defs.js';
|
||||
import { random_choice } from './util.js';
|
||||
|
||||
function activate_me(me, level) {
|
||||
@ -1141,12 +1141,15 @@ const TILE_TYPES = {
|
||||
teleport_blue: {
|
||||
draw_layer: DRAW_LAYERS.terrain,
|
||||
wire_propagation_mode: 'all',
|
||||
teleport_dest_order(me, level, other) {
|
||||
*teleport_dest_order(me, level, other) {
|
||||
let exit_direction = other.direction;
|
||||
if (! me.wire_directions) {
|
||||
// TODO cc2 has a bug where, once it wraps around to the bottom right, it seems to
|
||||
// forget that it was ever looking for an unwired teleport and will just grab the
|
||||
// first one it sees
|
||||
return level.iter_tiles_in_reading_order(me.cell, 'teleport_blue', true);
|
||||
for (let dest of level.iter_tiles_in_reading_order(me.cell, 'teleport_blue', true)) {
|
||||
yield [dest, exit_direction];
|
||||
}
|
||||
}
|
||||
|
||||
// Wired blue teleports form a network, which means we have to walk all wires from this
|
||||
@ -1225,13 +1228,14 @@ const TILE_TYPES = {
|
||||
) % level_size);
|
||||
}
|
||||
found.sort((a, b) => dest_indices.get(b) - dest_indices.get(a));
|
||||
return found;
|
||||
for (let dest of found) {
|
||||
yield [dest, exit_direction];
|
||||
}
|
||||
},
|
||||
},
|
||||
teleport_red: {
|
||||
draw_layer: DRAW_LAYERS.terrain,
|
||||
wire_propagation_mode: 'none',
|
||||
teleport_try_all_directions: true,
|
||||
teleport_allow_override: true,
|
||||
_is_active(me) {
|
||||
return ! (me.wire_directions && (me.cell.powered_edges & me.wire_directions) === 0);
|
||||
@ -1243,13 +1247,21 @@ const TILE_TYPES = {
|
||||
// has the bizarre behavior of NOT considering a red teleporter wired if none of its
|
||||
// wires are directly connected to another neighboring wire.
|
||||
// FIXME implement that, merge current code with is_cell_wired
|
||||
if (! this._is_active(me)) {
|
||||
yield me;
|
||||
return;
|
||||
let iterable;
|
||||
if (this._is_active(me)) {
|
||||
iterable = level.iter_tiles_in_reading_order(me.cell, 'teleport_red');
|
||||
}
|
||||
for (let tile of level.iter_tiles_in_reading_order(me.cell, 'teleport_red')) {
|
||||
else {
|
||||
iterable = [me];
|
||||
}
|
||||
let exit_direction = other.direction;
|
||||
for (let tile of iterable) {
|
||||
if (tile === me || this._is_active(tile)) {
|
||||
yield tile;
|
||||
// Red teleporters allow exiting in any direction, searching clockwise
|
||||
yield [tile, exit_direction];
|
||||
yield [tile, DIRECTIONS[exit_direction].right];
|
||||
yield [tile, DIRECTIONS[exit_direction].opposite];
|
||||
yield [tile, DIRECTIONS[exit_direction].left];
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -1262,28 +1274,38 @@ const TILE_TYPES = {
|
||||
},
|
||||
teleport_green: {
|
||||
draw_layer: DRAW_LAYERS.terrain,
|
||||
teleport_try_all_directions: true,
|
||||
teleport_dest_order(me, level, other) {
|
||||
let all = Array.from(level.iter_tiles_in_reading_order(me.cell, 'teleport_green'));
|
||||
if (all.length <= 1) {
|
||||
// If this is the only teleporter, just walk out the other side — and, crucially, do
|
||||
// NOT advance the PRNG
|
||||
return [me];
|
||||
return [[me, other.direction]];
|
||||
}
|
||||
// Note the iterator starts on the /next/ teleporter, so there's an implicit +1 here.
|
||||
// The -1 is to avoid spitting us back out of the same teleporter, which will be last in
|
||||
// the list
|
||||
let target = all[level.prng() % (all.length - 1)];
|
||||
// Also set the actor's (initial) exit direction
|
||||
level.set_actor_direction(other, ['north', 'east', 'south', 'west'][level.prng() % 4]);
|
||||
return [target, me];
|
||||
// Also pick the actor's exit direction
|
||||
let exit_direction = DIRECTION_ORDER[level.prng() % 4];
|
||||
return [
|
||||
// Green teleporters allow exiting in any direction, similar to red, but only on the
|
||||
// one they found; if that fails, you walk straight across the one you entered
|
||||
[target, exit_direction],
|
||||
[target, DIRECTIONS[exit_direction].right],
|
||||
[target, DIRECTIONS[exit_direction].opposite],
|
||||
[target, DIRECTIONS[exit_direction].left],
|
||||
[me, other.direction],
|
||||
];
|
||||
},
|
||||
},
|
||||
teleport_yellow: {
|
||||
draw_layer: DRAW_LAYERS.terrain,
|
||||
teleport_allow_override: true,
|
||||
teleport_dest_order(me, level, other) {
|
||||
return level.iter_tiles_in_reading_order(me.cell, 'teleport_yellow', true);
|
||||
*teleport_dest_order(me, level, other) {
|
||||
let exit_direction = other.direction;
|
||||
for (let dest of level.iter_tiles_in_reading_order(me.cell, 'teleport_yellow', true)) {
|
||||
yield [dest, exit_direction];
|
||||
}
|
||||
},
|
||||
},
|
||||
// Flame jet rules:
|
||||
@ -1363,7 +1385,7 @@ const TILE_TYPES = {
|
||||
for (let i = level.actors.length - 1; i >= 0; i--) {
|
||||
let actor = level.actors[i];
|
||||
// TODO generify somehow??
|
||||
if (actor.type.name === 'tank_yellow' && level.is_move_allowed(actor, other.direction, 'trace')) {
|
||||
if (actor.type.name === 'tank_yellow' && level.check_movement(actor, actor.cell, other.direction, 'trace')) {
|
||||
unblocked_tanks.push(actor);
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user