Move teleporter overriding to decision time; treat teleporting as a kind of slide; decouple speed from sliding
This commit is contained in:
parent
adb0c4c869
commit
c7815ba841
118
js/game.js
118
js/game.js
@ -277,7 +277,7 @@ export class Cell extends Array {
|
|||||||
level._set_tile_prop(actor, 'is_pushing', true);
|
level._set_tile_prop(actor, 'is_pushing', true);
|
||||||
}
|
}
|
||||||
if (! level.attempt_out_of_turn_step(tile, direction)) {
|
if (! level.attempt_out_of_turn_step(tile, direction)) {
|
||||||
if (tile.slide_mode !== null && tile.movement_cooldown !== 0) {
|
if (tile.slide_mode !== null && tile.movement_cooldown > 0) {
|
||||||
// If the push failed and the obstacle is in the middle of a slide,
|
// If the push failed and the obstacle is in the middle of a slide,
|
||||||
// remember this as the next move it'll make
|
// remember this as the next move it'll make
|
||||||
level._set_tile_prop(tile, 'pending_push', direction);
|
level._set_tile_prop(tile, 'pending_push', direction);
|
||||||
@ -1087,7 +1087,10 @@ export class Level extends LevelInterface {
|
|||||||
// succeed, even if overriding in the same direction we're already moving, that does count
|
// succeed, even if overriding in the same direction we're already moving, that does count
|
||||||
// as an override.
|
// as an override.
|
||||||
let terrain = actor.cell.get_terrain();
|
let terrain = actor.cell.get_terrain();
|
||||||
let may_move = ! forced_only && (! actor.slide_mode || (actor.slide_mode === 'force' && actor.last_move_was_force));
|
let may_move = ! forced_only && (
|
||||||
|
! actor.slide_mode ||
|
||||||
|
(actor.slide_mode === 'force' && actor.last_move_was_force) ||
|
||||||
|
(actor.slide_mode === 'teleport' && actor.cell.get_terrain().type.teleport_allow_override));
|
||||||
let [dir1, dir2] = this._extract_player_directions(input);
|
let [dir1, dir2] = this._extract_player_directions(input);
|
||||||
|
|
||||||
// Check for special player actions, which can only happen at decision time. Dropping can
|
// Check for special player actions, which can only happen at decision time. Dropping can
|
||||||
@ -1181,12 +1184,13 @@ export class Level extends LevelInterface {
|
|||||||
|
|
||||||
// If we're overriding a force floor but the direction we're moving in is blocked, the
|
// If we're overriding a force floor but the direction we're moving in is blocked, the
|
||||||
// force floor takes priority (and we've already bumped the wall(s))
|
// force floor takes priority (and we've already bumped the wall(s))
|
||||||
if (actor.slide_mode && ! open) {
|
if (actor.slide_mode === 'force' && ! open) {
|
||||||
this._set_tile_prop(actor, 'last_move_was_force', true);
|
this._set_tile_prop(actor, 'last_move_was_force', true);
|
||||||
actor.decision = actor.direction;
|
actor.decision = actor.direction;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// Otherwise this is 100% a conscious move so we lose our override power next tic
|
// Otherwise this is 100% a conscious move so we lose our override power next tic
|
||||||
|
// TODO how does this interact with teleports
|
||||||
this._set_tile_prop(actor, 'last_move_was_force', false);
|
this._set_tile_prop(actor, 'last_move_was_force', false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1292,9 +1296,10 @@ export class Level extends LevelInterface {
|
|||||||
}
|
}
|
||||||
this.set_actor_direction(actor, direction);
|
this.set_actor_direction(actor, direction);
|
||||||
|
|
||||||
// Record our speed, and halve it below if we're stepping onto a sliding tile
|
// Grab speed /first/, in case the movement or on_blocked turns us into an animation
|
||||||
|
// immediately (and then we won't have a speed!)
|
||||||
|
// FIXME that's a weird case actually since the explosion ends up still moving
|
||||||
let speed = actor.type.movement_speed;
|
let speed = actor.type.movement_speed;
|
||||||
let double_speed = false;
|
|
||||||
|
|
||||||
let move = DIRECTIONS[direction].movement;
|
let move = DIRECTIONS[direction].movement;
|
||||||
if (! this.check_movement(actor, actor.cell, direction, 'push')) {
|
if (! this.check_movement(actor, actor.cell, direction, 'push')) {
|
||||||
@ -1304,17 +1309,16 @@ export class Level extends LevelInterface {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We're clear! Compute our speed and move us
|
||||||
// FIXME this feels clunky
|
// FIXME this feels clunky
|
||||||
let goal_cell = this.get_neighboring_cell(actor.cell, direction);
|
let goal_cell = this.get_neighboring_cell(actor.cell, direction);
|
||||||
let terrain = goal_cell.get_terrain();
|
let terrain = goal_cell.get_terrain();
|
||||||
if (terrain && terrain.type.slide_mode && ! actor.ignores(terrain.type.name)) {
|
if (actor.has_item('speed_boots')) {
|
||||||
double_speed = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// We're clear!
|
|
||||||
if (double_speed || actor.has_item('speed_boots')) {
|
|
||||||
speed /= 2;
|
speed /= 2;
|
||||||
}
|
}
|
||||||
|
else if (terrain && terrain.type.speed_factor && ! actor.ignores(terrain.type.name)) {
|
||||||
|
speed /= terrain.type.speed_factor;
|
||||||
|
}
|
||||||
|
|
||||||
let orig_cell = actor.cell;
|
let orig_cell = actor.cell;
|
||||||
this._set_tile_prop(actor, 'previous_cell', orig_cell);
|
this._set_tile_prop(actor, 'previous_cell', orig_cell);
|
||||||
@ -1418,8 +1422,6 @@ export class Level extends LevelInterface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Announce we're approaching
|
// Announce we're approaching
|
||||||
// Clear the slide here since slide mode is important for knowing our speed
|
|
||||||
this.make_slide(actor, null);
|
|
||||||
for (let tile of Array.from(actor.cell)) {
|
for (let tile of Array.from(actor.cell)) {
|
||||||
if (tile === actor)
|
if (tile === actor)
|
||||||
continue;
|
continue;
|
||||||
@ -1445,9 +1447,6 @@ export class Level extends LevelInterface {
|
|||||||
if (tile.type.on_approach) {
|
if (tile.type.on_approach) {
|
||||||
tile.type.on_approach(tile, this, actor);
|
tile.type.on_approach(tile, this, actor);
|
||||||
}
|
}
|
||||||
if (tile.type.slide_mode) {
|
|
||||||
this.make_slide(actor, tile.type.slide_mode);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we're a monster stepping on the player's tail, that also kills her immediately; the
|
// If we're a monster stepping on the player's tail, that also kills her immediately; the
|
||||||
@ -1508,11 +1507,10 @@ export class Level extends LevelInterface {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
attempt_teleport(actor, input) {
|
attempt_teleport(actor) {
|
||||||
let teleporter = actor.just_stepped_on_teleporter;
|
let teleporter = actor.just_stepped_on_teleporter;
|
||||||
actor.just_stepped_on_teleporter = null;
|
delete actor.just_stepped_on_teleporter;
|
||||||
|
|
||||||
let push_mode = actor === this.player ? 'push' : 'bump';
|
|
||||||
let original_direction = actor.direction;
|
let original_direction = actor.direction;
|
||||||
let success = false;
|
let success = false;
|
||||||
let dest, direction;
|
let dest, direction;
|
||||||
@ -1528,7 +1526,15 @@ export class Level extends LevelInterface {
|
|||||||
if (dest === teleporter && teleporter.type.name === 'teleport_yellow') {
|
if (dest === teleporter && teleporter.type.name === 'teleport_yellow') {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (this.check_movement(actor, dest.cell, direction, push_mode)) {
|
// Note that this uses 'bump' even for players; it would be very bad if we could
|
||||||
|
// initiate movement in this pass (in Lexy rules, anyway), because we might try to push
|
||||||
|
// something that's still waiting to teleport itself!
|
||||||
|
// XXX is this correct? it does mean you won't try to teleport to a teleporter that's
|
||||||
|
// "blocked" by a block that won't be there anyway by the time you try to move, but that
|
||||||
|
// seems very obscure and i haven't run into a case with it yet. offhand i don't think
|
||||||
|
// it can even come up under cc2 rules, since teleporting is done after an actor cools
|
||||||
|
// down and before the next actor even gets a chance to act
|
||||||
|
if (this.check_movement(actor, dest.cell, direction, 'bump')) {
|
||||||
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
|
||||||
@ -1538,64 +1544,28 @@ export class Level extends LevelInterface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (success) {
|
if (success) {
|
||||||
if (teleporter.type.teleport_allow_override && actor === this.player) {
|
this.set_actor_direction(actor, direction);
|
||||||
// Red and yellow teleporters allow players to override the exit direction. This
|
this.make_slide(actor, 'teleport');
|
||||||
// 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(this.p1_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
|
// Now physically move the actor, but their movement waits until next decision phase
|
||||||
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.remove_tile(actor);
|
||||||
this.add_tile(actor, dest.cell);
|
this.add_tile(actor, dest.cell);
|
||||||
// FIXME i think the cc2 approach might be to handle this at decision time, hence why
|
|
||||||
// overriding works at all; it happens to work for me because this happens immediately
|
|
||||||
// before decision time as a separate pass! for now, simulate by undoing the cooldown
|
|
||||||
this.attempt_out_of_turn_step(actor, direction);
|
|
||||||
if (this.compat.use_lynx_loop && actor.movement_cooldown) {
|
|
||||||
this._set_tile_prop(actor, 'movement_cooldown', actor.movement_cooldown + 1);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (! success && actor.type.has_inventory && teleporter.type.name === 'teleport_yellow') {
|
else {
|
||||||
// Super duper special yellow teleporter behavior: you pick it the fuck up
|
// Be sure to remove the slide_mode or they'll be stuck forever
|
||||||
// FIXME not if there's only one in the level?
|
// TODO: in cc2, if you're on a /disabled/ red teleporter, you'll continue to be pushed
|
||||||
this.attempt_take(actor, teleporter);
|
// in this direction?? is this a bug, is this the only such case?
|
||||||
if (actor === this.player) {
|
if (! (teleporter.type.name === 'teleport_red' && ! teleporter.type._is_active(teleporter, this))) {
|
||||||
this.sfx.play_once('get-tool', teleporter.cell);
|
this.make_slide(actor, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (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?
|
||||||
|
this.attempt_take(actor, teleporter);
|
||||||
|
if (actor === this.player) {
|
||||||
|
this.sfx.play_once('get-tool', teleporter.cell);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -679,11 +679,13 @@ const TILE_TYPES = {
|
|||||||
ice: {
|
ice: {
|
||||||
draw_layer: DRAW_LAYERS.terrain,
|
draw_layer: DRAW_LAYERS.terrain,
|
||||||
slide_mode: 'ice',
|
slide_mode: 'ice',
|
||||||
|
speed_factor: 2,
|
||||||
},
|
},
|
||||||
ice_sw: {
|
ice_sw: {
|
||||||
draw_layer: DRAW_LAYERS.terrain,
|
draw_layer: DRAW_LAYERS.terrain,
|
||||||
thin_walls: new Set(['south', 'west']),
|
thin_walls: new Set(['south', 'west']),
|
||||||
slide_mode: 'ice',
|
slide_mode: 'ice',
|
||||||
|
speed_factor: 2,
|
||||||
blocks_leaving: blocks_leaving_thin_walls,
|
blocks_leaving: blocks_leaving_thin_walls,
|
||||||
on_arrive(me, level, other) {
|
on_arrive(me, level, other) {
|
||||||
if (other.direction === 'south') {
|
if (other.direction === 'south') {
|
||||||
@ -698,6 +700,7 @@ const TILE_TYPES = {
|
|||||||
draw_layer: DRAW_LAYERS.terrain,
|
draw_layer: DRAW_LAYERS.terrain,
|
||||||
thin_walls: new Set(['north', 'west']),
|
thin_walls: new Set(['north', 'west']),
|
||||||
slide_mode: 'ice',
|
slide_mode: 'ice',
|
||||||
|
speed_factor: 2,
|
||||||
blocks_leaving: blocks_leaving_thin_walls,
|
blocks_leaving: blocks_leaving_thin_walls,
|
||||||
on_arrive(me, level, other) {
|
on_arrive(me, level, other) {
|
||||||
if (other.direction === 'north') {
|
if (other.direction === 'north') {
|
||||||
@ -712,6 +715,7 @@ const TILE_TYPES = {
|
|||||||
draw_layer: DRAW_LAYERS.terrain,
|
draw_layer: DRAW_LAYERS.terrain,
|
||||||
thin_walls: new Set(['north', 'east']),
|
thin_walls: new Set(['north', 'east']),
|
||||||
slide_mode: 'ice',
|
slide_mode: 'ice',
|
||||||
|
speed_factor: 2,
|
||||||
blocks_leaving: blocks_leaving_thin_walls,
|
blocks_leaving: blocks_leaving_thin_walls,
|
||||||
on_arrive(me, level, other) {
|
on_arrive(me, level, other) {
|
||||||
if (other.direction === 'north') {
|
if (other.direction === 'north') {
|
||||||
@ -726,6 +730,7 @@ const TILE_TYPES = {
|
|||||||
draw_layer: DRAW_LAYERS.terrain,
|
draw_layer: DRAW_LAYERS.terrain,
|
||||||
thin_walls: new Set(['south', 'east']),
|
thin_walls: new Set(['south', 'east']),
|
||||||
slide_mode: 'ice',
|
slide_mode: 'ice',
|
||||||
|
speed_factor: 2,
|
||||||
blocks_leaving: blocks_leaving_thin_walls,
|
blocks_leaving: blocks_leaving_thin_walls,
|
||||||
on_arrive(me, level, other) {
|
on_arrive(me, level, other) {
|
||||||
if (other.direction === 'south') {
|
if (other.direction === 'south') {
|
||||||
@ -739,6 +744,7 @@ const TILE_TYPES = {
|
|||||||
force_floor_n: {
|
force_floor_n: {
|
||||||
draw_layer: DRAW_LAYERS.terrain,
|
draw_layer: DRAW_LAYERS.terrain,
|
||||||
slide_mode: 'force',
|
slide_mode: 'force',
|
||||||
|
speed_factor: 2,
|
||||||
on_ready: on_ready_force_floor,
|
on_ready: on_ready_force_floor,
|
||||||
on_arrive(me, level, other) {
|
on_arrive(me, level, other) {
|
||||||
level.set_actor_direction(other, 'north');
|
level.set_actor_direction(other, 'north');
|
||||||
@ -756,6 +762,7 @@ const TILE_TYPES = {
|
|||||||
force_floor_e: {
|
force_floor_e: {
|
||||||
draw_layer: DRAW_LAYERS.terrain,
|
draw_layer: DRAW_LAYERS.terrain,
|
||||||
slide_mode: 'force',
|
slide_mode: 'force',
|
||||||
|
speed_factor: 2,
|
||||||
on_ready: on_ready_force_floor,
|
on_ready: on_ready_force_floor,
|
||||||
on_arrive(me, level, other) {
|
on_arrive(me, level, other) {
|
||||||
level.set_actor_direction(other, 'east');
|
level.set_actor_direction(other, 'east');
|
||||||
@ -773,6 +780,7 @@ const TILE_TYPES = {
|
|||||||
force_floor_s: {
|
force_floor_s: {
|
||||||
draw_layer: DRAW_LAYERS.terrain,
|
draw_layer: DRAW_LAYERS.terrain,
|
||||||
slide_mode: 'force',
|
slide_mode: 'force',
|
||||||
|
speed_factor: 2,
|
||||||
on_ready: on_ready_force_floor,
|
on_ready: on_ready_force_floor,
|
||||||
on_arrive(me, level, other) {
|
on_arrive(me, level, other) {
|
||||||
level.set_actor_direction(other, 'south');
|
level.set_actor_direction(other, 'south');
|
||||||
@ -790,6 +798,7 @@ const TILE_TYPES = {
|
|||||||
force_floor_w: {
|
force_floor_w: {
|
||||||
draw_layer: DRAW_LAYERS.terrain,
|
draw_layer: DRAW_LAYERS.terrain,
|
||||||
slide_mode: 'force',
|
slide_mode: 'force',
|
||||||
|
speed_factor: 2,
|
||||||
on_ready: on_ready_force_floor,
|
on_ready: on_ready_force_floor,
|
||||||
on_arrive(me, level, other) {
|
on_arrive(me, level, other) {
|
||||||
level.set_actor_direction(other, 'west');
|
level.set_actor_direction(other, 'west');
|
||||||
@ -807,6 +816,7 @@ const TILE_TYPES = {
|
|||||||
force_floor_all: {
|
force_floor_all: {
|
||||||
draw_layer: DRAW_LAYERS.terrain,
|
draw_layer: DRAW_LAYERS.terrain,
|
||||||
slide_mode: 'force',
|
slide_mode: 'force',
|
||||||
|
speed_factor: 2,
|
||||||
on_ready: on_ready_force_floor,
|
on_ready: on_ready_force_floor,
|
||||||
// TODO ms: this is random, and an acting wall to monsters (!)
|
// TODO ms: this is random, and an acting wall to monsters (!)
|
||||||
on_arrive(me, level, other) {
|
on_arrive(me, level, other) {
|
||||||
@ -1194,6 +1204,7 @@ const TILE_TYPES = {
|
|||||||
},
|
},
|
||||||
teleport_blue: {
|
teleport_blue: {
|
||||||
draw_layer: DRAW_LAYERS.terrain,
|
draw_layer: DRAW_LAYERS.terrain,
|
||||||
|
slide_mode: 'teleport',
|
||||||
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;
|
let exit_direction = other.direction;
|
||||||
@ -1274,6 +1285,7 @@ const TILE_TYPES = {
|
|||||||
},
|
},
|
||||||
teleport_red: {
|
teleport_red: {
|
||||||
draw_layer: DRAW_LAYERS.terrain,
|
draw_layer: DRAW_LAYERS.terrain,
|
||||||
|
slide_mode: 'teleport',
|
||||||
wire_propagation_mode: 'none',
|
wire_propagation_mode: 'none',
|
||||||
teleport_allow_override: true,
|
teleport_allow_override: true,
|
||||||
_is_active(me, level) {
|
_is_active(me, level) {
|
||||||
@ -1319,6 +1331,7 @@ const TILE_TYPES = {
|
|||||||
},
|
},
|
||||||
teleport_green: {
|
teleport_green: {
|
||||||
draw_layer: DRAW_LAYERS.terrain,
|
draw_layer: DRAW_LAYERS.terrain,
|
||||||
|
slide_mode: 'teleport',
|
||||||
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) {
|
||||||
@ -1345,6 +1358,7 @@ const TILE_TYPES = {
|
|||||||
},
|
},
|
||||||
teleport_yellow: {
|
teleport_yellow: {
|
||||||
draw_layer: DRAW_LAYERS.terrain,
|
draw_layer: DRAW_LAYERS.terrain,
|
||||||
|
slide_mode: 'teleport',
|
||||||
teleport_allow_override: true,
|
teleport_allow_override: true,
|
||||||
*teleport_dest_order(me, level, other) {
|
*teleport_dest_order(me, level, other) {
|
||||||
let exit_direction = other.direction;
|
let exit_direction = other.direction;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user