From c7815ba8415e215bde39978365d055ad73982039 Mon Sep 17 00:00:00 2001 From: "Eevee (Evelyn Woods)" Date: Sun, 27 Dec 2020 05:40:06 -0700 Subject: [PATCH] Move teleporter overriding to decision time; treat teleporting as a kind of slide; decouple speed from sliding --- js/game.js | 118 ++++++++++++++++++------------------------------ js/tiletypes.js | 14 ++++++ 2 files changed, 58 insertions(+), 74 deletions(-) diff --git a/js/game.js b/js/game.js index ef9f056..383af9f 100644 --- a/js/game.js +++ b/js/game.js @@ -277,7 +277,7 @@ export class Cell extends Array { level._set_tile_prop(actor, 'is_pushing', true); } 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, // remember this as the next move it'll make 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 // as an override. 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); // 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 // 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); actor.decision = actor.direction; } else { // 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); } } @@ -1292,9 +1296,10 @@ export class Level extends LevelInterface { } 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 double_speed = false; let move = DIRECTIONS[direction].movement; if (! this.check_movement(actor, actor.cell, direction, 'push')) { @@ -1304,17 +1309,16 @@ export class Level extends LevelInterface { return false; } + // We're clear! Compute our speed and move us // FIXME this feels clunky let goal_cell = this.get_neighboring_cell(actor.cell, direction); let terrain = goal_cell.get_terrain(); - if (terrain && terrain.type.slide_mode && ! actor.ignores(terrain.type.name)) { - double_speed = true; - } - - // We're clear! - if (double_speed || actor.has_item('speed_boots')) { + if (actor.has_item('speed_boots')) { speed /= 2; } + else if (terrain && terrain.type.speed_factor && ! actor.ignores(terrain.type.name)) { + speed /= terrain.type.speed_factor; + } let orig_cell = actor.cell; this._set_tile_prop(actor, 'previous_cell', orig_cell); @@ -1418,8 +1422,6 @@ export class Level extends LevelInterface { } // 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)) { if (tile === actor) continue; @@ -1445,9 +1447,6 @@ export class Level extends LevelInterface { if (tile.type.on_approach) { 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 @@ -1508,11 +1507,10 @@ export class Level extends LevelInterface { } } - attempt_teleport(actor, input) { + attempt_teleport(actor) { 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 success = false; let dest, direction; @@ -1528,7 +1526,15 @@ export class Level extends LevelInterface { if (dest === teleporter && teleporter.type.name === 'teleport_yellow') { 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; // 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 @@ -1538,64 +1544,28 @@ export class Level extends LevelInterface { } 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(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); - } + this.set_actor_direction(actor, direction); + this.make_slide(actor, 'teleport'); - // 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 + // Now physically move the actor, but their movement waits until next decision phase this.remove_tile(actor); 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') { - // 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); + else { + // Be sure to remove the slide_mode or they'll be stuck forever + // TODO: in cc2, if you're on a /disabled/ red teleporter, you'll continue to be pushed + // in this direction?? is this a bug, is this the only such case? + if (! (teleporter.type.name === 'teleport_red' && ! teleporter.type._is_active(teleporter, this))) { + 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); + } } } } diff --git a/js/tiletypes.js b/js/tiletypes.js index b78ab2f..6bac1e6 100644 --- a/js/tiletypes.js +++ b/js/tiletypes.js @@ -679,11 +679,13 @@ const TILE_TYPES = { ice: { draw_layer: DRAW_LAYERS.terrain, slide_mode: 'ice', + speed_factor: 2, }, ice_sw: { draw_layer: DRAW_LAYERS.terrain, thin_walls: new Set(['south', 'west']), slide_mode: 'ice', + speed_factor: 2, blocks_leaving: blocks_leaving_thin_walls, on_arrive(me, level, other) { if (other.direction === 'south') { @@ -698,6 +700,7 @@ const TILE_TYPES = { draw_layer: DRAW_LAYERS.terrain, thin_walls: new Set(['north', 'west']), slide_mode: 'ice', + speed_factor: 2, blocks_leaving: blocks_leaving_thin_walls, on_arrive(me, level, other) { if (other.direction === 'north') { @@ -712,6 +715,7 @@ const TILE_TYPES = { draw_layer: DRAW_LAYERS.terrain, thin_walls: new Set(['north', 'east']), slide_mode: 'ice', + speed_factor: 2, blocks_leaving: blocks_leaving_thin_walls, on_arrive(me, level, other) { if (other.direction === 'north') { @@ -726,6 +730,7 @@ const TILE_TYPES = { draw_layer: DRAW_LAYERS.terrain, thin_walls: new Set(['south', 'east']), slide_mode: 'ice', + speed_factor: 2, blocks_leaving: blocks_leaving_thin_walls, on_arrive(me, level, other) { if (other.direction === 'south') { @@ -739,6 +744,7 @@ const TILE_TYPES = { force_floor_n: { draw_layer: DRAW_LAYERS.terrain, slide_mode: 'force', + speed_factor: 2, on_ready: on_ready_force_floor, on_arrive(me, level, other) { level.set_actor_direction(other, 'north'); @@ -756,6 +762,7 @@ const TILE_TYPES = { force_floor_e: { draw_layer: DRAW_LAYERS.terrain, slide_mode: 'force', + speed_factor: 2, on_ready: on_ready_force_floor, on_arrive(me, level, other) { level.set_actor_direction(other, 'east'); @@ -773,6 +780,7 @@ const TILE_TYPES = { force_floor_s: { draw_layer: DRAW_LAYERS.terrain, slide_mode: 'force', + speed_factor: 2, on_ready: on_ready_force_floor, on_arrive(me, level, other) { level.set_actor_direction(other, 'south'); @@ -790,6 +798,7 @@ const TILE_TYPES = { force_floor_w: { draw_layer: DRAW_LAYERS.terrain, slide_mode: 'force', + speed_factor: 2, on_ready: on_ready_force_floor, on_arrive(me, level, other) { level.set_actor_direction(other, 'west'); @@ -807,6 +816,7 @@ const TILE_TYPES = { force_floor_all: { draw_layer: DRAW_LAYERS.terrain, slide_mode: 'force', + speed_factor: 2, on_ready: on_ready_force_floor, // TODO ms: this is random, and an acting wall to monsters (!) on_arrive(me, level, other) { @@ -1194,6 +1204,7 @@ const TILE_TYPES = { }, teleport_blue: { draw_layer: DRAW_LAYERS.terrain, + slide_mode: 'teleport', wire_propagation_mode: 'all', *teleport_dest_order(me, level, other) { let exit_direction = other.direction; @@ -1274,6 +1285,7 @@ const TILE_TYPES = { }, teleport_red: { draw_layer: DRAW_LAYERS.terrain, + slide_mode: 'teleport', wire_propagation_mode: 'none', teleport_allow_override: true, _is_active(me, level) { @@ -1319,6 +1331,7 @@ const TILE_TYPES = { }, teleport_green: { draw_layer: DRAW_LAYERS.terrain, + slide_mode: 'teleport', teleport_dest_order(me, level, other) { let all = Array.from(level.iter_tiles_in_reading_order(me.cell, 'teleport_green')); if (all.length <= 1) { @@ -1345,6 +1358,7 @@ const TILE_TYPES = { }, teleport_yellow: { draw_layer: DRAW_LAYERS.terrain, + slide_mode: 'teleport', teleport_allow_override: true, *teleport_dest_order(me, level, other) { let exit_direction = other.direction;