Add teleport overriding and seriously clean up teleport code
This commit is contained in:
parent
7c82a4cdf9
commit
25b4b32f94
120
js/game.js
120
js/game.js
@ -631,7 +631,7 @@ export class Level {
|
|||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (actor.just_stepped_on_teleporter) {
|
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;
|
continue;
|
||||||
|
|
||||||
// Check for special player actions, which can only happen when not moving
|
// 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) {
|
if (actor === this.player) {
|
||||||
let new_input = p1_input & ~this.previous_input;
|
let new_input = p1_input & ~this.previous_input;
|
||||||
if (new_input & INPUT_BITS.cycle) {
|
if (new_input & INPUT_BITS.cycle) {
|
||||||
@ -768,13 +773,8 @@ export class Level {
|
|||||||
this.commit();
|
this.commit();
|
||||||
}
|
}
|
||||||
|
|
||||||
make_player_decision(actor, input) {
|
_extract_player_directions(input) {
|
||||||
// Only reset the player's is_pushing between movement, so it lasts for the whole push
|
// Extract directions from an input mask
|
||||||
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
|
|
||||||
let dir1 = null, dir2 = null;
|
let dir1 = null, dir2 = null;
|
||||||
if (((input & INPUT_BITS['up']) && (input & INPUT_BITS['down'])) ||
|
if (((input & INPUT_BITS['up']) && (input & INPUT_BITS['down'])) ||
|
||||||
((input & INPUT_BITS['left']) && (input & INPUT_BITS['right'])))
|
((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) => {
|
let try_direction = (direction, push_mode) => {
|
||||||
direction = actor.cell.redirect_exit(actor, direction);
|
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
|
// FIXME if the player steps into a monster cell here, they die instantly! but only
|
||||||
// if the cell doesn't block them??
|
// if the cell doesn't block them??
|
||||||
! dest_cell.blocks_entering(actor, direction, this, push_mode));
|
return this.check_movement(actor, actor.cell, direction, push_mode);
|
||||||
};
|
};
|
||||||
|
|
||||||
// The player is unusual in several ways.
|
// The player is unusual in several ways.
|
||||||
@ -957,7 +963,7 @@ export class Level {
|
|||||||
|
|
||||||
direction = actor.cell.redirect_exit(actor, direction);
|
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
|
// We found a good direction! Stop here
|
||||||
actor.decision = direction;
|
actor.decision = direction;
|
||||||
all_blocked = false;
|
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
|
// FIXME make this interact with railroads correctly and use it for players and in attempt_step
|
||||||
is_move_allowed(actor, direction, push_mode) {
|
check_movement(actor, orig_cell, direction, push_mode) {
|
||||||
let dest_cell = this.get_neighboring_cell(actor.cell, direction);
|
let dest_cell = this.get_neighboring_cell(orig_cell, direction);
|
||||||
return (dest_cell &&
|
return (dest_cell &&
|
||||||
! actor.cell.blocks_leaving(actor, direction) &&
|
! orig_cell.blocks_leaving(actor, direction) &&
|
||||||
! dest_cell.blocks_entering(actor, direction, this, push_mode));
|
! 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;
|
let teleporter = actor.just_stepped_on_teleporter;
|
||||||
actor.just_stepped_on_teleporter = null;
|
actor.just_stepped_on_teleporter = null;
|
||||||
|
|
||||||
// Handle teleporting, now that the dust has cleared
|
let push_mode = actor === this.player ? 'move' : 'trace';
|
||||||
// FIXME something funny happening here, your input isn't ignored while walking out of it?
|
|
||||||
let original_direction = actor.direction;
|
let original_direction = actor.direction;
|
||||||
let success = false;
|
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
|
// Teleporters already containing an actor are blocked and unusable
|
||||||
// FIXME should check collision?
|
// FIXME should check collision?
|
||||||
if (dest.cell.some(tile => tile.type.is_actor && tile !== actor))
|
if (dest.cell.some(tile => tile.type.is_actor && tile !== actor))
|
||||||
continue;
|
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 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
|
// FIXME bleugh hardcode
|
||||||
if (dest === teleporter && teleporter.type.name === 'teleport_yellow') {
|
if (dest === teleporter && teleporter.type.name === 'teleport_yellow') {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
for (let i = 0; i < num_directions; i++) {
|
if (this.check_movement(actor, dest.cell, direction, push_mode)) {
|
||||||
if (this.attempt_out_of_turn_step(actor, direction)) {
|
|
||||||
success = true;
|
success = true;
|
||||||
// Sound plays from the origin cell simply because that's where the sfx player
|
// 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
|
// thinks the player is currently; position isn't updated til next turn
|
||||||
this.sfx.play_once('teleport', teleporter.cell);
|
this.sfx.play_once('teleport', teleporter.cell);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
direction = DIRECTIONS[direction].right;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (success) {
|
if (success) {
|
||||||
break;
|
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);
|
||||||
}
|
}
|
||||||
else if (num_directions === 4) {
|
if (dir2) {
|
||||||
// Restore our original facing before continuing
|
open2 = this.check_movement(actor, dest.cell, dir2, push_mode);
|
||||||
// (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
|
// If the player didn't even try to override, do nothing
|
||||||
// movement direction, so this is still correct.)
|
if (! dir1 && ! dir2) {
|
||||||
this.set_actor_direction(actor, original_direction);
|
}
|
||||||
|
// 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') {
|
if (! success && actor.type.has_inventory && teleporter.type.name === 'teleport_yellow') {
|
||||||
// Super duper special yellow teleporter behavior: you pick it the fuck up
|
// Super duper special yellow teleporter behavior: you pick it the fuck up
|
||||||
// FIXME not if there's only one in the level?
|
// 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';
|
import { random_choice } from './util.js';
|
||||||
|
|
||||||
function activate_me(me, level) {
|
function activate_me(me, level) {
|
||||||
@ -1141,12 +1141,15 @@ const TILE_TYPES = {
|
|||||||
teleport_blue: {
|
teleport_blue: {
|
||||||
draw_layer: DRAW_LAYERS.terrain,
|
draw_layer: DRAW_LAYERS.terrain,
|
||||||
wire_propagation_mode: 'all',
|
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) {
|
if (! me.wire_directions) {
|
||||||
// TODO cc2 has a bug where, once it wraps around to the bottom right, it seems to
|
// 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
|
// forget that it was ever looking for an unwired teleport and will just grab the
|
||||||
// first one it sees
|
// 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
|
// 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);
|
) % level_size);
|
||||||
}
|
}
|
||||||
found.sort((a, b) => dest_indices.get(b) - dest_indices.get(a));
|
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: {
|
teleport_red: {
|
||||||
draw_layer: DRAW_LAYERS.terrain,
|
draw_layer: DRAW_LAYERS.terrain,
|
||||||
wire_propagation_mode: 'none',
|
wire_propagation_mode: 'none',
|
||||||
teleport_try_all_directions: true,
|
|
||||||
teleport_allow_override: true,
|
teleport_allow_override: true,
|
||||||
_is_active(me) {
|
_is_active(me) {
|
||||||
return ! (me.wire_directions && (me.cell.powered_edges & me.wire_directions) === 0);
|
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
|
// has the bizarre behavior of NOT considering a red teleporter wired if none of its
|
||||||
// wires are directly connected to another neighboring wire.
|
// wires are directly connected to another neighboring wire.
|
||||||
// FIXME implement that, merge current code with is_cell_wired
|
// FIXME implement that, merge current code with is_cell_wired
|
||||||
if (! this._is_active(me)) {
|
let iterable;
|
||||||
yield me;
|
if (this._is_active(me)) {
|
||||||
return;
|
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)) {
|
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: {
|
teleport_green: {
|
||||||
draw_layer: DRAW_LAYERS.terrain,
|
draw_layer: DRAW_LAYERS.terrain,
|
||||||
teleport_try_all_directions: true,
|
|
||||||
teleport_dest_order(me, level, other) {
|
teleport_dest_order(me, level, other) {
|
||||||
let all = Array.from(level.iter_tiles_in_reading_order(me.cell, 'teleport_green'));
|
let all = Array.from(level.iter_tiles_in_reading_order(me.cell, 'teleport_green'));
|
||||||
if (all.length <= 1) {
|
if (all.length <= 1) {
|
||||||
// If this is the only teleporter, just walk out the other side — and, crucially, do
|
// If this is the only teleporter, just walk out the other side — and, crucially, do
|
||||||
// NOT advance the PRNG
|
// 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.
|
// 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 -1 is to avoid spitting us back out of the same teleporter, which will be last in
|
||||||
// the list
|
// the list
|
||||||
let target = all[level.prng() % (all.length - 1)];
|
let target = all[level.prng() % (all.length - 1)];
|
||||||
// Also set the actor's (initial) exit direction
|
// Also pick the actor's exit direction
|
||||||
level.set_actor_direction(other, ['north', 'east', 'south', 'west'][level.prng() % 4]);
|
let exit_direction = DIRECTION_ORDER[level.prng() % 4];
|
||||||
return [target, me];
|
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: {
|
teleport_yellow: {
|
||||||
draw_layer: DRAW_LAYERS.terrain,
|
draw_layer: DRAW_LAYERS.terrain,
|
||||||
teleport_allow_override: true,
|
teleport_allow_override: true,
|
||||||
teleport_dest_order(me, level, other) {
|
*teleport_dest_order(me, level, other) {
|
||||||
return level.iter_tiles_in_reading_order(me.cell, 'teleport_yellow', true);
|
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:
|
// Flame jet rules:
|
||||||
@ -1363,7 +1385,7 @@ const TILE_TYPES = {
|
|||||||
for (let i = level.actors.length - 1; i >= 0; i--) {
|
for (let i = level.actors.length - 1; i >= 0; i--) {
|
||||||
let actor = level.actors[i];
|
let actor = level.actors[i];
|
||||||
// TODO generify somehow??
|
// 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);
|
unblocked_tanks.push(actor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user