Fix a few more Lynx compat issues
This commit is contained in:
parent
2dcd73d44a
commit
63609ba77e
@ -178,6 +178,10 @@ export const COMPAT_FLAGS = [
|
||||
key: 'traps_like_lynx',
|
||||
label: "Traps eject faster, and even when already open",
|
||||
rulesets: new Set(['lynx']),
|
||||
}, {
|
||||
key: 'blue_floors_vanish_on_arrive',
|
||||
label: "Fake blue walls vanish on arrival",
|
||||
rulesets: new Set(['lynx']),
|
||||
},
|
||||
|
||||
// Items
|
||||
@ -217,6 +221,10 @@ export const COMPAT_FLAGS = [
|
||||
key: 'tanks_teeth_push_ice_blocks',
|
||||
label: "Ice blocks emulate pgchip rules",
|
||||
rulesets: new Set(['ms']),
|
||||
}, {
|
||||
key: 'allow_pushing_blocks_off_faux_walls',
|
||||
label: "Blocks may be pushed off of blue (fake), invisible, and revealing walls",
|
||||
rulesets: new Set(['lynx']),
|
||||
}, {
|
||||
key: 'emulate_spring_mining',
|
||||
label: "Spring mining is possible",
|
||||
|
||||
61
js/game.js
61
js/game.js
@ -134,7 +134,7 @@ export class Tile {
|
||||
return false;
|
||||
}
|
||||
|
||||
can_push(tile, direction) {
|
||||
can_push(tile, direction, level) {
|
||||
// This tile already has a push queued, sorry
|
||||
if (tile.pending_push)
|
||||
return false;
|
||||
@ -158,7 +158,7 @@ export class Tile {
|
||||
// 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
|
||||
// XXX this expects to take a level but it only matters with push_mode === 'push'
|
||||
return tile.cell.try_leaving(tile, direction);
|
||||
return tile.cell.try_leaving(tile, direction, level);
|
||||
}
|
||||
|
||||
can_pull(tile, direction) {
|
||||
@ -261,7 +261,7 @@ export class Cell extends Array {
|
||||
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, actor)) {
|
||||
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)) {
|
||||
@ -302,6 +302,7 @@ export class Cell extends Array {
|
||||
// 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])
|
||||
@ -310,6 +311,7 @@ export class Cell extends Array {
|
||||
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);
|
||||
@ -325,7 +327,7 @@ export class Cell extends Array {
|
||||
if (push_mode === null)
|
||||
return false;
|
||||
|
||||
if (actor.can_push(tile, direction) || (
|
||||
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')
|
||||
)) {
|
||||
@ -338,6 +340,13 @@ export class Cell extends Array {
|
||||
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;
|
||||
}
|
||||
@ -417,7 +426,7 @@ export class Cell extends Array {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
return ! still_blocked;
|
||||
}
|
||||
|
||||
// Special railroad ability: change the direction we attempt to leave
|
||||
@ -1271,6 +1280,14 @@ export class Level extends LevelInterface {
|
||||
terrain.type.on_stand(terrain, this, actor);
|
||||
}
|
||||
}
|
||||
// Lynx gives everything in an open trap an extra cooldown, which makes things walk into
|
||||
// open traps at double speed and does weird things to the ejection timing
|
||||
if (this.compat.traps_like_lynx) {
|
||||
let terrain = actor.cell.get_terrain();
|
||||
if (terrain && terrain.type.name === 'trap' && terrain.presses > 0) {
|
||||
this._do_extra_cooldown(actor);
|
||||
}
|
||||
}
|
||||
if (actor.just_stepped_on_teleporter) {
|
||||
this.attempt_teleport(actor);
|
||||
}
|
||||
@ -1336,13 +1353,16 @@ export class Level extends LevelInterface {
|
||||
}
|
||||
|
||||
// Strip out any destroyed actors from the acting order
|
||||
if (! this.compat.reuse_actor_slots) {
|
||||
// FIXME this is O(n), where n is /usually/ small, but i still don't love it. not strictly
|
||||
// necessary, either; maybe only do it every few tics?
|
||||
let p = 0;
|
||||
for (let i = 0, l = this.actors.length; i < l; i++) {
|
||||
let actor = this.actors[i];
|
||||
if (actor.cell) {
|
||||
if (actor.cell || (
|
||||
// Don't strip out actors under Lynx, where slots were reused -- unless they're VFX,
|
||||
// which aren't in the original game and thus are exempt
|
||||
this.compat.reuse_actor_slots && actor.type.layer !== LAYERS.vfx))
|
||||
{
|
||||
if (p !== i) {
|
||||
this.actors[p] = actor;
|
||||
}
|
||||
@ -1354,7 +1374,6 @@ export class Level extends LevelInterface {
|
||||
}
|
||||
}
|
||||
this.actors.length = p;
|
||||
}
|
||||
|
||||
// Advance the clock
|
||||
// TODO i suspect cc2 does this at the beginning of the tic, but even if you've won? if you
|
||||
@ -1574,7 +1593,7 @@ export class Level extends LevelInterface {
|
||||
}
|
||||
if (forced_only)
|
||||
return;
|
||||
if (terrain.type.traps && terrain.type.traps(terrain, actor)) {
|
||||
if (terrain.type.traps && terrain.type.traps(terrain, this, actor)) {
|
||||
// An actor in a cloner or a closed trap can't turn
|
||||
// TODO because of this, if a tank is trapped when a blue button is pressed, then
|
||||
// when released, it will make one move out of the trap and /then/ turn around and
|
||||
@ -1665,9 +1684,7 @@ export class Level extends LevelInterface {
|
||||
|
||||
// Try to move the given actor one tile in the given direction and update their cooldown.
|
||||
// Return true if successful.
|
||||
// ('frameskip' is an absolute number of frames subtracted from the normal speed, only used for
|
||||
// Lynx's odd trap ejection behavior.)
|
||||
attempt_step(actor, direction, frameskip = 0) {
|
||||
attempt_step(actor, direction) {
|
||||
// In mid-movement, we can't even change direction!
|
||||
if (actor.movement_cooldown > 0)
|
||||
return false;
|
||||
@ -1713,7 +1730,7 @@ export class Level extends LevelInterface {
|
||||
|
||||
let orig_cell = actor.cell;
|
||||
this._set_tile_prop(actor, 'previous_cell', orig_cell);
|
||||
let duration = Math.max(3, speed * 3 - frameskip);
|
||||
let duration = speed * 3;
|
||||
this._set_tile_prop(actor, 'movement_cooldown', duration);
|
||||
this._set_tile_prop(actor, 'movement_speed', duration);
|
||||
this.move_to(actor, goal_cell);
|
||||
@ -1739,14 +1756,14 @@ export class Level extends LevelInterface {
|
||||
return true;
|
||||
}
|
||||
|
||||
attempt_out_of_turn_step(actor, direction, frameskip = 0) {
|
||||
attempt_out_of_turn_step(actor, direction) {
|
||||
if (actor.slide_mode === 'turntable') {
|
||||
// Something is (e.g.) pushing a block that just landed on a turntable and is waiting to
|
||||
// slide out of it. Ignore the push direction and move in its current direction;
|
||||
// otherwise a player will push a block straight through, then turn, which sucks
|
||||
direction = actor.direction;
|
||||
}
|
||||
let success = this.attempt_step(actor, direction, frameskip);
|
||||
let success = this.attempt_step(actor, direction);
|
||||
if (success) {
|
||||
this._do_extra_cooldown(actor);
|
||||
}
|
||||
@ -2653,8 +2670,9 @@ export class Level extends LevelInterface {
|
||||
}
|
||||
|
||||
add_actor(actor) {
|
||||
if (this.compat.reuse_actor_slots) {
|
||||
// Place the new actor in the first slot taken up by a nonexistent one
|
||||
if (this.compat.reuse_actor_slots && actor.type.layer !== LAYERS.vfx) {
|
||||
// Place the new actor in the first slot taken up by a nonexistent one, but not VFX
|
||||
// which aren't supposed to impact gameplay
|
||||
for (let i = 0, l = this.actors.length; i < l; i++) {
|
||||
let old_actor = this.actors[i];
|
||||
if (old_actor !== this.player && ! old_actor.cell) {
|
||||
@ -2677,9 +2695,12 @@ export class Level extends LevelInterface {
|
||||
let duration = tile.type.ttl;
|
||||
if (this.compat.force_lynx_animation_lengths) {
|
||||
// Lynx animation duration is 12 tics, but it drops one if necessary to make the
|
||||
// animation end on an even tic (???) and that takes step parity into account
|
||||
// because I guess it uses the global clock (?????????????????)
|
||||
duration = (12 - (this.tic_counter + this.step_parity) % 1) * 3;
|
||||
// animation end on an odd tic (???) and that takes step parity into account
|
||||
// because I guess it uses the global clock (?????????????????). Also, unlike CC2, Lynx
|
||||
// animations are removed once their cooldown goes BELOW zero, so to simulate that we
|
||||
// make the animation one tic longer.
|
||||
// XXX wait am i sure that cc2 doesn't work that way too?
|
||||
duration = (12 + (this.tic_counter + this.step_parity) % 2) * 3;
|
||||
}
|
||||
this._set_tile_prop(tile, 'movement_speed', duration);
|
||||
this._set_tile_prop(tile, 'movement_cooldown', duration);
|
||||
|
||||
@ -407,13 +407,22 @@ const TILE_TYPES = {
|
||||
fake_floor: {
|
||||
layer: LAYERS.terrain,
|
||||
blocks_collision: COLLISION.block_cc1 | COLLISION.monster_general,
|
||||
on_bumped(me, level, other) {
|
||||
if (other.type.can_reveal_walls) {
|
||||
reveal(me, level, other) {
|
||||
level.spawn_animation(me.cell, 'puff');
|
||||
level.transmute_tile(me, 'floor');
|
||||
if (other === level.player) {
|
||||
level.sfx.play_once('fake-floor', me.cell);
|
||||
}
|
||||
},
|
||||
on_bumped(me, level, other) {
|
||||
if (other.type.can_reveal_walls && ! level.compat.blue_floors_vanish_on_arrive) {
|
||||
this.reveal(me, level, other);
|
||||
}
|
||||
},
|
||||
on_arrive(me, level, other) {
|
||||
// In Lynx, these disappear only when you step on them
|
||||
if (level.compat.blue_floors_vanish_on_arrive) {
|
||||
this.reveal(me, level, other);
|
||||
}
|
||||
},
|
||||
},
|
||||
@ -1465,8 +1474,8 @@ const TILE_TYPES = {
|
||||
on_ready(me, level) {
|
||||
me.arrows = me.arrows ?? 0;
|
||||
},
|
||||
traps(me, actor) {
|
||||
return ! actor._clone_release;
|
||||
traps(me, level, other) {
|
||||
return ! other._clone_release;
|
||||
},
|
||||
activate(me, level, aggressive = false) {
|
||||
let actor = me.cell.get_actor();
|
||||
@ -1532,10 +1541,9 @@ const TILE_TYPES = {
|
||||
}
|
||||
},
|
||||
on_arrive(me, level, other) {
|
||||
// Lynx (not cc2): open traps immediately eject their contents on arrival, if possible,
|
||||
// and also do it slightly faster
|
||||
// Lynx (not cc2): open traps immediately eject their contents on arrival, if possible
|
||||
if (level.compat.traps_like_lynx) {
|
||||
level.attempt_out_of_turn_step(other, other.direction, 3);
|
||||
level.attempt_out_of_turn_step(other, other.direction);
|
||||
}
|
||||
},
|
||||
add_press_ready(me, level, other) {
|
||||
@ -1544,17 +1552,13 @@ const TILE_TYPES = {
|
||||
},
|
||||
add_press(me, level, is_wire = false) {
|
||||
level._set_tile_prop(me, 'presses', me.presses + 1);
|
||||
// TODO weird cc2 case that may or may not be a bug: actors aren't ejected if the trap
|
||||
// opened because of wiring
|
||||
if (me.presses === 1 && ! is_wire) {
|
||||
// Free any actor on us, if we went from 0 to 1 presses (i.e. closed to open)
|
||||
let actor = me.cell.get_actor();
|
||||
if (actor) {
|
||||
// Forcibly move anything released from a trap, which keeps it in sync with
|
||||
// whatever pushed the button
|
||||
level.attempt_out_of_turn_step(
|
||||
actor, actor.direction,
|
||||
level.compat.traps_like_lynx ? 3 : 0);
|
||||
level.attempt_out_of_turn_step(actor, actor.direction);
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -1565,8 +1569,19 @@ const TILE_TYPES = {
|
||||
}
|
||||
},
|
||||
// FIXME also doesn't trap ghosts, is that a special case???
|
||||
traps(me, actor) {
|
||||
return ! me.presses && ! me._initially_open && actor.type.name !== 'ghost';
|
||||
traps(me, level, other) {
|
||||
if (level.compat.traps_like_lynx) {
|
||||
// Lynx traps don't actually track open vs closed; actors are just ejected by force
|
||||
// by a separate pass at the end of the tic that checks what's on a brown button.
|
||||
// That means a trap held open by a button at level start won't effectively be open
|
||||
// if whatever's on the button moves within the first tic, a quirk that CCLXP2 #17
|
||||
// Double Trouble critically relies on!
|
||||
// To fix this, assume that a trap can never be released on the first turn.
|
||||
// FIXME that's not right since a block or immobile mob might be on a button...
|
||||
if (level.tic_counter === 0)
|
||||
return true;
|
||||
}
|
||||
return ! me.presses && ! me._initially_open && other.type.name !== 'ghost';
|
||||
},
|
||||
on_power(me, level) {
|
||||
// Treat being powered or not as an extra kind of brown button press
|
||||
|
||||
Loading…
Reference in New Issue
Block a user