Move the hairy Cell collision methods into Level
This commit is contained in:
parent
49b691adde
commit
24a55d7c88
408
js/game.js
408
js/game.js
@ -157,8 +157,7 @@ export class Tile {
|
|||||||
direction = tile.cell.redirect_exit(tile, direction);
|
direction = tile.cell.redirect_exit(tile, direction);
|
||||||
// Need to explicitly check this here, otherwise you could /attempt/ to push a block,
|
// Need to explicitly check this here, otherwise you could /attempt/ to push a block,
|
||||||
// which would fail, but it would still change the block's direction
|
// which would fail, but it would still change the block's direction
|
||||||
// XXX this expects to take a level but it only matters with push_mode === 'push'
|
return level.can_actor_leave_cell(tile, tile.cell, direction);
|
||||||
return tile.cell.try_leaving(tile, direction, level);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
can_pull(tile, direction) {
|
can_pull(tile, direction) {
|
||||||
@ -250,185 +249,6 @@ export class Cell extends Array {
|
|||||||
return current && current.type.name === name;
|
return current && current.type.name === name;
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME honestly no longer sure why these two are on Cell, or even separate really
|
|
||||||
try_leaving(actor, direction, level, push_mode) {
|
|
||||||
// The only tiles that can trap us are thin walls and terrain, so for perf (this is very hot
|
|
||||||
// code), only bother checking those)
|
|
||||||
let terrain = this[LAYERS.terrain];
|
|
||||||
let thin_walls = this[LAYERS.thin_wall];
|
|
||||||
let blocker;
|
|
||||||
|
|
||||||
if (thin_walls && thin_walls.type.blocks_leaving && thin_walls.type.blocks_leaving(thin_walls, actor, direction)) {
|
|
||||||
blocker = thin_walls;
|
|
||||||
}
|
|
||||||
else if (terrain.type.traps && terrain.type.traps(terrain, level, actor)) {
|
|
||||||
blocker = terrain;
|
|
||||||
}
|
|
||||||
else if (terrain.type.blocks_leaving && terrain.type.blocks_leaving(terrain, actor, direction)) {
|
|
||||||
blocker = terrain;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (blocker) {
|
|
||||||
if (push_mode === 'push') {
|
|
||||||
if (actor.type.on_blocked) {
|
|
||||||
actor.type.on_blocked(actor, level, direction, blocker);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if this actor can move this direction into this cell. Returns true on success. May
|
|
||||||
// have side effects, depending on the value of push_mode:
|
|
||||||
// - null: Default. Do not impact game state. Treat pushable objects as blocking.
|
|
||||||
// - 'bump': Fire bump triggers. Don't move pushable objects, but do check whether they /could/
|
|
||||||
// be pushed, recursively if necessary.
|
|
||||||
// - 'slap': Like 'bump', but also sets the 'decision' of pushable objects.
|
|
||||||
// - 'push': Fire bump triggers. Attempt to move pushable objects out of the way immediately.
|
|
||||||
try_entering(actor, direction, level, push_mode = null) {
|
|
||||||
let pushable_tiles = [];
|
|
||||||
// Subtleties ahoy! This is **EXTREMELY** sensitive to ordering. Consider:
|
|
||||||
// - An actor with foil MUST NOT bump a wall on the other side of a thin wall.
|
|
||||||
// - A ghost with foil MUST bump a wall (even on the other side of a thin wall) and be
|
|
||||||
// deflected by the resulting steel.
|
|
||||||
// - A bowling ball MUST NOT destroy an actor on the other side of a thin wall, or on top of
|
|
||||||
// a regular wall.
|
|
||||||
// - A fireball MUST melt an ice block AND ALSO still be deflected by it, even if the ice
|
|
||||||
// block is on top of an item (which blocks the fireball), but NOT one on the other side
|
|
||||||
// of a thin wall.
|
|
||||||
// - A rover MUST NOT bump walls underneath a canopy (which blocks it).
|
|
||||||
// It seems the order is thus: canopy + thin wall; terrain; actor; item. Which is the usual
|
|
||||||
// ordering from the top down, except that terrain is checked before actors. Really, the
|
|
||||||
// ordering is from "outermost" to "innermost", which makes physical sense.
|
|
||||||
let still_blocked = false;
|
|
||||||
for (let layer of [
|
|
||||||
LAYERS.canopy, LAYERS.thin_wall, LAYERS.terrain, LAYERS.swivel,
|
|
||||||
LAYERS.actor, LAYERS.item_mod, LAYERS.item])
|
|
||||||
{
|
|
||||||
let tile = this[layer];
|
|
||||||
if (! tile)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
let original_name = tile.type.name;
|
|
||||||
// TODO check ignores here?
|
|
||||||
if (tile.type.on_bumped) {
|
|
||||||
tile.type.on_bumped(tile, level, actor);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (! tile.blocks(actor, direction, level))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (tile.type.on_after_bumped) {
|
|
||||||
tile.type.on_after_bumped(tile, level, actor);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (push_mode === null)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
if (actor.can_push(tile, direction, level) || (
|
|
||||||
level.compat.tanks_teeth_push_ice_blocks && tile.type.name === 'ice_block' &&
|
|
||||||
(actor.type.name === 'teeth' || actor.type.name === 'teeth_timid' || actor.type.name === 'tank_blue')
|
|
||||||
)) {
|
|
||||||
// Collect pushables for later, so we don't inadvertently push through a wall
|
|
||||||
pushable_tiles.push(tile);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// It's in our way and we can't push it, so we're done here
|
|
||||||
if (push_mode === 'push') {
|
|
||||||
if (actor.type.on_blocked) {
|
|
||||||
actor.type.on_blocked(actor, level, direction, tile);
|
|
||||||
}
|
|
||||||
// Lynx (or at least TW?) allows pushing blocks off of particular wall types
|
|
||||||
if (level.compat.allow_pushing_blocks_off_faux_walls &&
|
|
||||||
['fake_wall', 'wall_invisible', 'wall_appearing'].includes(original_name))
|
|
||||||
{
|
|
||||||
still_blocked = true;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we got this far, all that's left is to deal with pushables
|
|
||||||
if (pushable_tiles.length > 0) {
|
|
||||||
// This ends recursive push attempts, which can happen with a row of ice clogged by ice
|
|
||||||
// blocks that are trying to slide
|
|
||||||
actor._trying_to_push = true;
|
|
||||||
try {
|
|
||||||
for (let tile of pushable_tiles) {
|
|
||||||
if (tile._trying_to_push)
|
|
||||||
return false;
|
|
||||||
if (push_mode === 'bump' || push_mode === 'slap') {
|
|
||||||
// FIXME this doesn't take railroad curves into account, e.g. it thinks a
|
|
||||||
// rover can't push a block through a curve
|
|
||||||
if (tile.movement_cooldown > 0 ||
|
|
||||||
! level.check_movement(tile, tile.cell, direction, push_mode))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
else if (push_mode === 'slap') {
|
|
||||||
if (actor === level.player) {
|
|
||||||
level._set_tile_prop(actor, 'is_pushing', true);
|
|
||||||
level.sfx.play_once('push');
|
|
||||||
}
|
|
||||||
tile.decision = direction;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (push_mode === 'push') {
|
|
||||||
if (actor === level.player) {
|
|
||||||
level._set_tile_prop(actor, 'is_pushing', true);
|
|
||||||
}
|
|
||||||
// We can't directly push a sliding block, even one on a force floor that's
|
|
||||||
// stuck on a wall. Instead, it becomes a pending move for the block, which
|
|
||||||
// will use this as a decision next time it's allowed to move
|
|
||||||
// FIXME this is clumsy and creates behavior dependent on actor order. my
|
|
||||||
// original implementation only did this if the push /failed/; is that worth
|
|
||||||
// a compat option? also, how does any of this work under lynx rules?
|
|
||||||
if (tile.slide_mode === 'force' ||
|
|
||||||
(tile.slide_mode !== null && tile.movement_cooldown > 0))
|
|
||||||
{
|
|
||||||
level._set_tile_prop(tile, 'pending_push', direction);
|
|
||||||
// FIXME if the block has already made a decision then this is necessary
|
|
||||||
// to override it. but i don't like it; (a) it might cause blocks to
|
|
||||||
// get stuck against walls on force floors, because the code to fix that
|
|
||||||
// is at decision time; (b) it's done for pulling too and just feels
|
|
||||||
// hacky?
|
|
||||||
tile.decision = direction;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (level.attempt_out_of_turn_step(tile, direction)) {
|
|
||||||
if (actor === level.player) {
|
|
||||||
level.sfx.play_once('push');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
finally {
|
|
||||||
delete actor._trying_to_push;
|
|
||||||
}
|
|
||||||
|
|
||||||
// In push mode, check one last time for being blocked, in case we e.g. pushed a block
|
|
||||||
// off of a recessed wall
|
|
||||||
// TODO unclear if this is the right way to emulate spring mining, but without the check
|
|
||||||
// for a player, it happens /too/ often; try allowing for ann actors and running the 163
|
|
||||||
// BLOX replay, and right at the end ice blocks spring mine each other. also, the wiki
|
|
||||||
// suggests something about another actor moving away at the same time?
|
|
||||||
if (! (level.compat.emulate_spring_mining && actor.type.is_real_player) &&
|
|
||||||
push_mode === 'push' && this.some(tile => tile && tile.blocks(actor, direction, level)))
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return ! still_blocked;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Special railroad ability: change the direction we attempt to leave
|
// Special railroad ability: change the direction we attempt to leave
|
||||||
redirect_exit(actor, direction) {
|
redirect_exit(actor, direction) {
|
||||||
let terrain = this.get_terrain();
|
let terrain = this.get_terrain();
|
||||||
@ -462,6 +282,8 @@ export class Level extends LevelInterface {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Level setup ------------------------------------------------------------------------------------
|
||||||
|
|
||||||
restart(compat) {
|
restart(compat) {
|
||||||
this.compat = compat;
|
this.compat = compat;
|
||||||
|
|
||||||
@ -853,6 +675,8 @@ export class Level extends LevelInterface {
|
|||||||
this.player.slide_mode === 'force' && this.player.last_move_was_force));
|
this.player.slide_mode === 'force' && this.player.last_move_was_force));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Randomness -------------------------------------------------------------------------------------
|
||||||
|
|
||||||
// Lynx PRNG, used unchanged in CC2
|
// Lynx PRNG, used unchanged in CC2
|
||||||
prng() {
|
prng() {
|
||||||
let n = (this._rng1 >> 2) - this._rng1;
|
let n = (this._rng1 >> 2) - this._rng1;
|
||||||
@ -879,10 +703,8 @@ export class Level extends LevelInterface {
|
|||||||
let mod = this._blob_modifier;
|
let mod = this._blob_modifier;
|
||||||
|
|
||||||
if (this.stored_level.blob_behavior === 1) {
|
if (this.stored_level.blob_behavior === 1) {
|
||||||
// "4 patterns" just increments by 1 every time (but /after/ returning)
|
// "4 patterns" just increments by 1 every time
|
||||||
//this._blob_modifier = (this._blob_modifier + 1) % 4;
|
|
||||||
mod = (mod + 1) % 4;
|
mod = (mod + 1) % 4;
|
||||||
this._blob_modifier = mod;
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// Other modes do this curious operation
|
// Other modes do this curious operation
|
||||||
@ -891,12 +713,14 @@ export class Level extends LevelInterface {
|
|||||||
mod ^= 0x1d;
|
mod ^= 0x1d;
|
||||||
}
|
}
|
||||||
mod &= 0xff;
|
mod &= 0xff;
|
||||||
this._blob_modifier = mod;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this._blob_modifier = mod;
|
||||||
return mod;
|
return mod;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Main loop --------------------------------------------------------------------------------------
|
||||||
|
|
||||||
// Move the game state forwards by one tic.
|
// Move the game state forwards by one tic.
|
||||||
// Input is a bit mask of INPUT_BITS.
|
// Input is a bit mask of INPUT_BITS.
|
||||||
advance_tic(p1_input) {
|
advance_tic(p1_input) {
|
||||||
@ -1369,7 +1193,7 @@ export class Level extends LevelInterface {
|
|||||||
if (p !== i) {
|
if (p !== i) {
|
||||||
this.actors[p] = actor;
|
this.actors[p] = actor;
|
||||||
}
|
}
|
||||||
p++;
|
p += 1;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
let local_p = p;
|
let local_p = p;
|
||||||
@ -1637,6 +1461,186 @@ export class Level extends LevelInterface {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Actor movement ---------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
can_actor_leave_cell(actor, cell, direction, push_mode) {
|
||||||
|
// The only tiles that can trap us are thin walls and terrain, so for perf (this is very hot
|
||||||
|
// code), only bother checking those)
|
||||||
|
let terrain = cell[LAYERS.terrain];
|
||||||
|
let thin_walls = cell[LAYERS.thin_wall];
|
||||||
|
let blocker;
|
||||||
|
|
||||||
|
if (thin_walls && thin_walls.type.blocks_leaving && thin_walls.type.blocks_leaving(thin_walls, actor, direction)) {
|
||||||
|
blocker = thin_walls;
|
||||||
|
}
|
||||||
|
else if (terrain.type.traps && terrain.type.traps(terrain, this, actor)) {
|
||||||
|
blocker = terrain;
|
||||||
|
}
|
||||||
|
else if (terrain.type.blocks_leaving && terrain.type.blocks_leaving(terrain, actor, direction)) {
|
||||||
|
blocker = terrain;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (blocker) {
|
||||||
|
if (push_mode === 'push') {
|
||||||
|
if (actor.type.on_blocked) {
|
||||||
|
actor.type.on_blocked(actor, this, direction, blocker);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if this actor can move this direction into this cell. Returns true on success. May
|
||||||
|
// have side effects, depending on the value of push_mode:
|
||||||
|
// - null: Default. Do not impact game state. Treat pushable objects as blocking.
|
||||||
|
// - 'bump': Fire bump triggers. Don't move pushable objects, but do check whether they /could/
|
||||||
|
// be pushed, recursively if necessary.
|
||||||
|
// - 'slap': Like 'bump', but also sets the 'decision' of pushable objects.
|
||||||
|
// - 'push': Fire bump triggers. Attempt to move pushable objects out of the way immediately.
|
||||||
|
can_actor_enter_cell(actor, cell, direction, push_mode = null) {
|
||||||
|
let pushable_tiles = [];
|
||||||
|
// Subtleties ahoy! This is **EXTREMELY** sensitive to ordering. Consider:
|
||||||
|
// - An actor with foil MUST NOT bump a wall on the other side of a thin wall.
|
||||||
|
// - A ghost with foil MUST bump a wall (even on the other side of a thin wall) and be
|
||||||
|
// deflected by the resulting steel.
|
||||||
|
// - A bowling ball MUST NOT destroy an actor on the other side of a thin wall, or on top of
|
||||||
|
// a regular wall.
|
||||||
|
// - A fireball MUST melt an ice block AND ALSO still be deflected by it, even if the ice
|
||||||
|
// block is on top of an item (which blocks the fireball), but NOT one on the other side
|
||||||
|
// of a thin wall.
|
||||||
|
// - A rover MUST NOT bump walls underneath a canopy (which blocks it).
|
||||||
|
// It seems the order is thus: canopy + thin wall; terrain; actor; item. Which is the usual
|
||||||
|
// ordering from the top down, except that terrain is checked before actors. Really, the
|
||||||
|
// ordering is from "outermost" to "innermost", which makes physical sense.
|
||||||
|
let still_blocked = false;
|
||||||
|
for (let layer of [
|
||||||
|
LAYERS.canopy, LAYERS.thin_wall, LAYERS.terrain, LAYERS.swivel,
|
||||||
|
LAYERS.actor, LAYERS.item_mod, LAYERS.item])
|
||||||
|
{
|
||||||
|
let tile = cell[layer];
|
||||||
|
if (! tile)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
let original_name = tile.type.name;
|
||||||
|
// TODO check ignores here?
|
||||||
|
if (tile.type.on_bumped) {
|
||||||
|
tile.type.on_bumped(tile, this, actor);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! tile.blocks(actor, direction, this))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (tile.type.on_after_bumped) {
|
||||||
|
tile.type.on_after_bumped(tile, this, actor);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (push_mode === null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (actor.can_push(tile, direction, this) || (
|
||||||
|
this.compat.tanks_teeth_push_ice_blocks && tile.type.name === 'ice_block' &&
|
||||||
|
(actor.type.name === 'teeth' || actor.type.name === 'teeth_timid' || actor.type.name === 'tank_blue')
|
||||||
|
)) {
|
||||||
|
// Collect pushables for later, so we don't inadvertently push through a wall
|
||||||
|
pushable_tiles.push(tile);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// It's in our way and we can't push it, so we're done here
|
||||||
|
if (push_mode === 'push') {
|
||||||
|
if (actor.type.on_blocked) {
|
||||||
|
actor.type.on_blocked(actor, this, direction, tile);
|
||||||
|
}
|
||||||
|
// Lynx (or at least TW?) allows pushing blocks off of particular wall types
|
||||||
|
if (this.compat.allow_pushing_blocks_off_faux_walls &&
|
||||||
|
['fake_wall', 'wall_invisible', 'wall_appearing'].includes(original_name))
|
||||||
|
{
|
||||||
|
still_blocked = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we got this far, all that's left is to deal with pushables
|
||||||
|
if (pushable_tiles.length > 0) {
|
||||||
|
// This ends recursive push attempts, which can happen with a row of ice clogged by ice
|
||||||
|
// blocks that are trying to slide
|
||||||
|
actor._trying_to_push = true;
|
||||||
|
try {
|
||||||
|
for (let tile of pushable_tiles) {
|
||||||
|
if (tile._trying_to_push)
|
||||||
|
return false;
|
||||||
|
if (push_mode === 'bump' || push_mode === 'slap') {
|
||||||
|
// FIXME this doesn't take railroad curves into account, e.g. it thinks a
|
||||||
|
// rover can't push a block through a curve
|
||||||
|
if (tile.movement_cooldown > 0 ||
|
||||||
|
! this.check_movement(tile, tile.cell, direction, push_mode))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else if (push_mode === 'slap') {
|
||||||
|
if (actor === this.player) {
|
||||||
|
this._set_tile_prop(actor, 'is_pushing', true);
|
||||||
|
this.sfx.play_once('push');
|
||||||
|
}
|
||||||
|
tile.decision = direction;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (push_mode === 'push') {
|
||||||
|
if (actor === this.player) {
|
||||||
|
this._set_tile_prop(actor, 'is_pushing', true);
|
||||||
|
}
|
||||||
|
// We can't directly push a sliding block, even one on a force floor that's
|
||||||
|
// stuck on a wall. Instead, it becomes a pending move for the block, which
|
||||||
|
// will use this as a decision next time it's allowed to move
|
||||||
|
// FIXME this is clumsy and creates behavior dependent on actor order. my
|
||||||
|
// original implementation only did this if the push /failed/; is that worth
|
||||||
|
// a compat option? also, how does any of this work under lynx rules?
|
||||||
|
if (tile.slide_mode === 'force' ||
|
||||||
|
(tile.slide_mode !== null && tile.movement_cooldown > 0))
|
||||||
|
{
|
||||||
|
this._set_tile_prop(tile, 'pending_push', direction);
|
||||||
|
// FIXME if the block has already made a decision then this is necessary
|
||||||
|
// to override it. but i don't like it; (a) it might cause blocks to
|
||||||
|
// get stuck against walls on force floors, because the code to fix that
|
||||||
|
// is at decision time; (b) it's done for pulling too and just feels
|
||||||
|
// hacky?
|
||||||
|
tile.decision = direction;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.attempt_out_of_turn_step(tile, direction)) {
|
||||||
|
if (actor === this.player) {
|
||||||
|
this.sfx.play_once('push');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
delete actor._trying_to_push;
|
||||||
|
}
|
||||||
|
|
||||||
|
// In push mode, check one last time for being blocked, in case we e.g. pushed a block
|
||||||
|
// off of a recessed wall
|
||||||
|
// TODO unclear if this is the right way to emulate spring mining, but without the check
|
||||||
|
// for a player, it happens /too/ often; try allowing for ann actors and running the 163
|
||||||
|
// BLOX replay, and right at the end ice blocks spring mine each other. also, the wiki
|
||||||
|
// suggests something about another actor moving away at the same time?
|
||||||
|
if (! (this.compat.emulate_spring_mining && actor.type.is_real_player) &&
|
||||||
|
push_mode === 'push' && cell.some(tile => tile && tile.blocks(actor, direction, this)))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ! still_blocked;
|
||||||
|
}
|
||||||
|
|
||||||
check_movement(actor, orig_cell, direction, push_mode) {
|
check_movement(actor, orig_cell, direction, push_mode) {
|
||||||
// Lynx: Players can't override backwards on force floors, and it functions like blocking,
|
// Lynx: Players can't override backwards on force floors, and it functions like blocking,
|
||||||
// but does NOT act like a bonk (hence why it's here)
|
// but does NOT act like a bonk (hence why it's here)
|
||||||
@ -1657,8 +1661,8 @@ export class Level extends LevelInterface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let success = (
|
let success = (
|
||||||
orig_cell.try_leaving(actor, direction, this, push_mode) &&
|
this.can_actor_leave_cell(actor, orig_cell, direction, push_mode) &&
|
||||||
dest_cell.try_entering(actor, direction, this, push_mode));
|
this.can_actor_enter_cell(actor, dest_cell, direction, push_mode));
|
||||||
|
|
||||||
// If we have the hook, pull anything behind us, now that we're out of the way.
|
// If we have the hook, pull anything behind us, now that we're out of the way.
|
||||||
// In CC2, this has to happen here to make hook-slapping work and allow hooking a moving
|
// In CC2, this has to happen here to make hook-slapping work and allow hooking a moving
|
||||||
@ -1989,7 +1993,6 @@ export class Level extends LevelInterface {
|
|||||||
// teleporting through it) it may not have been applied
|
// teleporting through it) it may not have been applied
|
||||||
this.make_slide(actor, 'teleport');
|
this.make_slide(actor, 'teleport');
|
||||||
|
|
||||||
let original_direction = actor.direction;
|
|
||||||
let success = false;
|
let success = false;
|
||||||
let dest, direction;
|
let dest, direction;
|
||||||
for ([dest, direction] of teleporter.type.teleport_dest_order(teleporter, this, actor)) {
|
for ([dest, direction] of teleporter.type.teleport_dest_order(teleporter, this, actor)) {
|
||||||
@ -2059,6 +2062,8 @@ export class Level extends LevelInterface {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Inventory handling -----------------------------------------------------------------------------
|
||||||
|
|
||||||
cycle_inventory(actor) {
|
cycle_inventory(actor) {
|
||||||
if (this.stored_level.use_cc1_boots)
|
if (this.stored_level.use_cc1_boots)
|
||||||
return;
|
return;
|
||||||
@ -2147,6 +2152,8 @@ export class Level extends LevelInterface {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Wiring -----------------------------------------------------------------------------------------
|
||||||
|
|
||||||
_do_wire_phase() {
|
_do_wire_phase() {
|
||||||
let force_next_wire_phase = false;
|
let force_next_wire_phase = false;
|
||||||
if (this.recalculate_circuitry_next_wire_phase)
|
if (this.recalculate_circuitry_next_wire_phase)
|
||||||
@ -2290,13 +2297,10 @@ export class Level extends LevelInterface {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
// Level inspection -------------------------------------------------------------------------------
|
||||||
// Board inspection
|
|
||||||
|
|
||||||
get_neighboring_cell(cell, direction) {
|
get_neighboring_cell(cell, direction) {
|
||||||
let move = DIRECTIONS[direction].movement;
|
let move = DIRECTIONS[direction].movement;
|
||||||
let goal_x = cell.x + move[0];
|
|
||||||
let goal_y = cell.y + move[1];
|
|
||||||
return this.cell(cell.x + move[0], cell.y + move[1]);
|
return this.cell(cell.x + move[0], cell.y + move[1]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2390,8 +2394,7 @@ export class Level extends LevelInterface {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
// Undo/redo --------------------------------------------------------------------------------------
|
||||||
// Undo handling
|
|
||||||
|
|
||||||
create_undo_entry() {
|
create_undo_entry() {
|
||||||
let entry = [];
|
let entry = [];
|
||||||
@ -2467,10 +2470,9 @@ export class Level extends LevelInterface {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
// Level alteration -------------------------------------------------------------------------------
|
||||||
// Level alteration methods. EVERYTHING that changes the state of a level,
|
// EVERYTHING that changes the state of a level, including the state of a single tile, should do
|
||||||
// including the state of a single tile, should do it through one of these
|
// it through one of these for undo/rewind purposes
|
||||||
// for undo/rewind purposes
|
|
||||||
|
|
||||||
_set_tile_prop(tile, key, val) {
|
_set_tile_prop(tile, key, val) {
|
||||||
if (Number.isNaN(val)) throw new Error(`got a NaN for ${key} on ${tile.type.name} at ${tile.cell.x}, ${tile.cell.y}`);
|
if (Number.isNaN(val)) throw new Error(`got a NaN for ${key} on ${tile.type.name} at ${tile.cell.x}, ${tile.cell.y}`);
|
||||||
@ -2598,11 +2600,15 @@ export class Level extends LevelInterface {
|
|||||||
|
|
||||||
this._push_pending_undo(() => {
|
this._push_pending_undo(() => {
|
||||||
this.fail_reason = null;
|
this.fail_reason = null;
|
||||||
if (player != null) { player.fail_reason = null; }
|
if (player) {
|
||||||
|
player.fail_reason = null;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
this.state = 'failure';
|
this.state = 'failure';
|
||||||
this.fail_reason = reason;
|
this.fail_reason = reason;
|
||||||
if (player != null) { player.fail_reason = reason; }
|
if (player) {
|
||||||
|
player.fail_reason = reason;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
win() {
|
win() {
|
||||||
|
|||||||
@ -28,7 +28,7 @@ function on_begin_force_floor(me, level) {
|
|||||||
// doing this here; decision time hasn't happened yet, but we need to know what direction we're
|
// doing this here; decision time hasn't happened yet, but we need to know what direction we're
|
||||||
// moving to know whether bestowal happens? so what IS the cause of item bestowal?
|
// moving to know whether bestowal happens? so what IS the cause of item bestowal?
|
||||||
let neighbor = level.get_neighboring_cell(me.cell, actor.direction);
|
let neighbor = level.get_neighboring_cell(me.cell, actor.direction);
|
||||||
if (neighbor && neighbor.try_entering(actor, actor.direction, level))
|
if (neighbor && level.can_actor_enter_cell(actor, neighbor, actor.direction))
|
||||||
return;
|
return;
|
||||||
let item = me.cell.get_item();
|
let item = me.cell.get_item();
|
||||||
if (! item)
|
if (! item)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user