Merge remote-tracking branch 'upstream/master'
This commit is contained in:
commit
509b3ca3b7
@ -38,7 +38,9 @@ Give it a try, I guess! [https://c.eev.ee/lexys-labyrinth/](https://c.eev.ee/le
|
|||||||
|
|
||||||
## Special thanks
|
## Special thanks
|
||||||
|
|
||||||
- The incredible nerds who put together the [Chip Wiki](https://wiki.bitbusters.club/) and also reside on the Bit Busters Discord
|
- The incredible nerds who put together the [Chip Wiki](https://wiki.bitbusters.club/) and also reside on the Bit Busters Discord, including:
|
||||||
|
- ruben for documenting the CC2 PRNG
|
||||||
|
- The Architect for documenting the CC2 C2G parser
|
||||||
- Everyone who worked on [Chip's Challenge Level Pack 1](https://wiki.bitbusters.club/Chip%27s_Challenge_Level_Pack_1), the default set of levels
|
- Everyone who worked on [Chip's Challenge Level Pack 1](https://wiki.bitbusters.club/Chip%27s_Challenge_Level_Pack_1), the default set of levels
|
||||||
- [Tile World](https://wiki.bitbusters.club/Tile_World) for being an incredible reference on Lynx mechanics
|
- [Tile World](https://wiki.bitbusters.club/Tile_World) for being an incredible reference on Lynx mechanics
|
||||||
- Everyone who contributed music — see [`js/soundtrack.js`](js/soundtrack.js) for a list!
|
- Everyone who contributed music — see [`js/soundtrack.js`](js/soundtrack.js) for a list!
|
||||||
|
|||||||
@ -16,6 +16,10 @@ export class StoredLevel {
|
|||||||
this.extra_chunks = [];
|
this.extra_chunks = [];
|
||||||
this.use_cc1_boots = false;
|
this.use_cc1_boots = false;
|
||||||
this.use_ccl_compat = false;
|
this.use_ccl_compat = false;
|
||||||
|
// 0 - deterministic (PRNG + simple convolution)
|
||||||
|
// 1 - 4 patterns (default; PRNG + rotating through 0-3)
|
||||||
|
// 2 - extra random (like deterministic, but initial seed is "actually" random)
|
||||||
|
this.blob_behavior = 1;
|
||||||
|
|
||||||
this.size_x = 0;
|
this.size_x = 0;
|
||||||
this.size_y = 0;
|
this.size_y = 0;
|
||||||
|
|||||||
@ -536,7 +536,7 @@ const TILE_ENCODING = {
|
|||||||
name: 'popdown_floor',
|
name: 'popdown_floor',
|
||||||
},
|
},
|
||||||
0x7f: {
|
0x7f: {
|
||||||
name: 'forbidden',
|
name: 'no_sign',
|
||||||
has_next: true,
|
has_next: true,
|
||||||
},
|
},
|
||||||
0x80: {
|
0x80: {
|
||||||
@ -835,7 +835,7 @@ export function parse_level(buf, number = 1) {
|
|||||||
|
|
||||||
if (view.byteLength <= 24)
|
if (view.byteLength <= 24)
|
||||||
continue;
|
continue;
|
||||||
//level.blob_behavior = view.getUint8(24, true);
|
level.blob_behavior = view.getUint8(24, true);
|
||||||
}
|
}
|
||||||
else if (type === 'MAP ' || type === 'PACK') {
|
else if (type === 'MAP ' || type === 'PACK') {
|
||||||
if (type === 'PACK') {
|
if (type === 'PACK') {
|
||||||
|
|||||||
151
js/game.js
151
js/game.js
@ -212,6 +212,16 @@ export class Level {
|
|||||||
this.hint_shown = null;
|
this.hint_shown = null;
|
||||||
// TODO in lynx/steam, this carries over between levels; in tile world, you can set it manually
|
// TODO in lynx/steam, this carries over between levels; in tile world, you can set it manually
|
||||||
this.force_floor_direction = 'north';
|
this.force_floor_direction = 'north';
|
||||||
|
// PRNG is initialized to zero
|
||||||
|
this._rng1 = 0;
|
||||||
|
this._rng2 = 0;
|
||||||
|
if (this.stored_level.blob_behavior === 0) {
|
||||||
|
this._blob_modifier = 0x55;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// The other two modes are initialized to a random seed
|
||||||
|
this._blob_modifier = Math.floor(Math.random() * 256);
|
||||||
|
}
|
||||||
|
|
||||||
this.undo_stack = [];
|
this.undo_stack = [];
|
||||||
this.pending_undo = [];
|
this.pending_undo = [];
|
||||||
@ -322,15 +332,62 @@ export class Level {
|
|||||||
if (tile.type.on_ready) {
|
if (tile.type.on_ready) {
|
||||||
tile.type.on_ready(tile, this);
|
tile.type.on_ready(tile, this);
|
||||||
}
|
}
|
||||||
|
if (cell === this.player.cell && tile.type.is_hint) {
|
||||||
|
this.hint_shown = tile.specific_hint ?? this.stored_level.hint;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
player_awaiting_input() {
|
player_awaiting_input() {
|
||||||
return this.player.movement_cooldown === 0 && (this.player.slide_mode === null || (this.player.slide_mode === 'force' && this.player.last_move_was_force))
|
return this.player.movement_cooldown === 0 && (this.player.slide_mode === null || (this.player.slide_mode === 'force' && this.player.last_move_was_force))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Lynx PRNG, used unchanged in CC2
|
||||||
|
prng() {
|
||||||
|
// TODO what if we just saved this stuff, as well as the RFF direction, at the beginning of
|
||||||
|
// each tic?
|
||||||
|
let rng1 = this._rng1;
|
||||||
|
let rng2 = this._rng2;
|
||||||
|
this.pending_undo.push(() => {
|
||||||
|
this._rng1 = rng1;
|
||||||
|
this._rng2 = rng2;
|
||||||
|
});
|
||||||
|
|
||||||
|
let n = (this._rng1 >> 2) - this._rng1;
|
||||||
|
if (!(this._rng1 & 0x02)) --n;
|
||||||
|
this._rng1 = (this._rng1 >> 1) | (this._rng2 & 0x80);
|
||||||
|
this._rng2 = (this._rng2 << 1) | (n & 0x01);
|
||||||
|
let ret = (this._rng1 ^ this._rng2) & 0xff;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Weird thing done by CC2 to make blobs... more... random
|
||||||
|
get_blob_modifier() {
|
||||||
|
let mod = this._blob_modifier;
|
||||||
|
this.pending_undo.push(() => this._blob_modifier = mod);
|
||||||
|
|
||||||
|
if (this.stored_level.blob_behavior === 1) {
|
||||||
|
// "4 patterns" just increments by 1 every time (but /after/ returning)
|
||||||
|
//this._blob_modifier = (this._blob_modifier + 1) % 4;
|
||||||
|
mod = (mod + 1) % 4;
|
||||||
|
this._blob_modifier = mod;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Other modes do this curious operation
|
||||||
|
mod *= 2;
|
||||||
|
if (mod < 255) {
|
||||||
|
mod ^= 0x1d;
|
||||||
|
}
|
||||||
|
mod &= 0xff;
|
||||||
|
this._blob_modifier = mod;
|
||||||
|
}
|
||||||
|
|
||||||
|
return mod;
|
||||||
|
}
|
||||||
|
|
||||||
// Move the game state forwards by one tic
|
// Move the game state forwards by one tic
|
||||||
// split into two parts for turn-based mode: first part is the consequences of the previous tick, second part depends on the player's input
|
// split into two parts for turn-based mode: first part is the consequences of the previous tick, second part depends on the player's input
|
||||||
advance_tic(p1_primary_direction, p1_secondary_direction, pass) {
|
advance_tic(p1_primary_direction, p1_secondary_direction, pass) {
|
||||||
@ -649,11 +706,9 @@ export class Level {
|
|||||||
direction_preference = [actor.direction, d.opposite];
|
direction_preference = [actor.direction, d.opposite];
|
||||||
}
|
}
|
||||||
else if (actor.type.movement_mode === 'bounce-random') {
|
else if (actor.type.movement_mode === 'bounce-random') {
|
||||||
// walker behavior: preserve current direction; if that
|
// walker behavior: preserve current direction; if that doesn't work, pick a random
|
||||||
// doesn't work, pick a random direction, even the one we
|
// direction, even the one we failed to move in (but ONLY then)
|
||||||
// failed to move in
|
direction_preference = [actor.direction, 'WALKER'];
|
||||||
// TODO unclear if this is right in cc2 as well. definitely not in ms, which chooses a legal move
|
|
||||||
direction_preference = [actor.direction, ['north', 'south', 'east', 'west'][Math.floor(Math.random() * 4)]];
|
|
||||||
}
|
}
|
||||||
else if (actor.type.movement_mode === 'pursue') {
|
else if (actor.type.movement_mode === 'pursue') {
|
||||||
// teeth behavior: always move towards the player
|
// teeth behavior: always move towards the player
|
||||||
@ -692,14 +747,27 @@ export class Level {
|
|||||||
}
|
}
|
||||||
else if (actor.type.movement_mode === 'random') {
|
else if (actor.type.movement_mode === 'random') {
|
||||||
// blob behavior: move completely at random
|
// blob behavior: move completely at random
|
||||||
// TODO cc2 has twiddles for how this works per-level, as well as the initial seed for demo playback
|
let modifier = this.get_blob_modifier();
|
||||||
direction_preference = [['north', 'south', 'east', 'west'][Math.floor(Math.random() * 4)]];
|
direction_preference = [['north', 'east', 'south', 'west'][(this.prng() + modifier) % 4]];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check which of those directions we *can*, probably, move in
|
// Check which of those directions we *can*, probably, move in
|
||||||
// TODO i think player on force floor will still have some issues here
|
// TODO i think player on force floor will still have some issues here
|
||||||
|
// FIXME probably bail earlier for stuck actors so the prng isn't advanced? what is the
|
||||||
|
// lynx behavior? also i hear something about blobs on cloners??
|
||||||
if (direction_preference && ! actor.stuck) {
|
if (direction_preference && ! actor.stuck) {
|
||||||
|
let fallback_direction;
|
||||||
for (let direction of direction_preference) {
|
for (let direction of direction_preference) {
|
||||||
|
if (direction === 'WALKER') {
|
||||||
|
// Walkers roll a random direction ONLY if their first attempt was blocked
|
||||||
|
direction = actor.direction;
|
||||||
|
let num_turns = this.prng() % 4;
|
||||||
|
for (let i = 0; i < num_turns; i++) {
|
||||||
|
direction = DIRECTIONS[direction].right;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fallback_direction = direction;
|
||||||
|
|
||||||
let dest_cell = this.get_neighboring_cell(actor.cell, direction);
|
let dest_cell = this.get_neighboring_cell(actor.cell, direction);
|
||||||
if (! dest_cell)
|
if (! dest_cell)
|
||||||
continue;
|
continue;
|
||||||
@ -712,6 +780,12 @@ export class Level {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If all the decisions are blocked, actors still try the last one (and might even
|
||||||
|
// be able to move that way by the time their turn comes around!)
|
||||||
|
if (actor.decision === null) {
|
||||||
|
actor.decision = fallback_direction;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -917,14 +991,15 @@ export class Level {
|
|||||||
continue;
|
continue;
|
||||||
|
|
||||||
// TODO some actors can pick up some items...
|
// TODO some actors can pick up some items...
|
||||||
if (actor.type.is_player && tile.type.is_item && this.give_actor(actor, tile.type.name)) {
|
if (actor.type.is_player && tile.type.is_item &&
|
||||||
|
this.attempt_take(actor, tile))
|
||||||
|
{
|
||||||
if (tile.type.is_key) {
|
if (tile.type.is_key) {
|
||||||
this.sfx.play_once('get-key', cell);
|
this.sfx.play_once('get-key', cell);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
this.sfx.play_once('get-tool', cell);
|
this.sfx.play_once('get-tool', cell);
|
||||||
}
|
}
|
||||||
this.remove_tile(tile);
|
|
||||||
}
|
}
|
||||||
else if (tile.type.teleport_dest_order) {
|
else if (tile.type.teleport_dest_order) {
|
||||||
teleporter = tile;
|
teleporter = tile;
|
||||||
@ -935,25 +1010,55 @@ export class Level {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Handle teleporting, now that the dust has cleared
|
// Handle teleporting, now that the dust has cleared
|
||||||
// FIXME something funny happening here, your input isn't ignore while walking out of it?
|
// FIXME something funny happening here, your input isn't ignored while walking out of it?
|
||||||
if (teleporter) {
|
if (teleporter) {
|
||||||
for (let dest of teleporter.type.teleport_dest_order(teleporter, this)) {
|
let original_direction = actor.direction;
|
||||||
|
let success = false;
|
||||||
|
for (let dest 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
|
||||||
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
|
// Physically move the actor to the new teleporter
|
||||||
// XXX is this right, compare with tile world? i overhear it's actually implemented as a slide?
|
// XXX lynx treats this as a slide and does it in a pass in the main loop
|
||||||
// XXX not especially undo-efficient
|
// XXX not especially undo-efficient
|
||||||
this.remove_tile(actor);
|
this.remove_tile(actor);
|
||||||
this.add_tile(actor, dest.cell);
|
this.add_tile(actor, dest.cell);
|
||||||
if (this.attempt_step(actor, actor.direction)) {
|
|
||||||
// Success, teleportation complete
|
// Red and green teleporters attempt to spit you out in every direction before
|
||||||
// Sound plays from the origin cell simply because that's where the sfx player
|
// giving up on a destination (but not if you return to the original).
|
||||||
// thinks the player is currently; position isn't updated til next turn
|
// Note that we use actor.direction here (rather than original_direction) because
|
||||||
this.sfx.play_once('teleport', dest.cell);
|
// 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;
|
||||||
|
}
|
||||||
|
for (let i = 0; i < num_directions; i++) {
|
||||||
|
if (this.attempt_step(actor, direction)) {
|
||||||
|
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
|
||||||
|
this.sfx.play_once('teleport', teleporter.cell);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
direction = DIRECTIONS[direction].right;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (success) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
else if (num_directions === 4) {
|
||||||
|
// Restore our original facing before continuing
|
||||||
|
// (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
|
||||||
|
// movement direction, so this is still correct.)
|
||||||
|
this.set_actor_direction(actor, original_direction);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1297,6 +1402,18 @@ export class Level {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Have an actor try to pick up a particular tile; it's prevented if there's a no sign, and the
|
||||||
|
// tile is removed if successful
|
||||||
|
attempt_take(actor, tile) {
|
||||||
|
if (! tile.cell.some(t => t.type.disables_pickup) &&
|
||||||
|
this.give_actor(actor, tile.type.name))
|
||||||
|
{
|
||||||
|
this.remove_tile(tile);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// Give an item to an actor, even if it's not supposed to have an inventory
|
// Give an item to an actor, even if it's not supposed to have an inventory
|
||||||
give_actor(actor, name) {
|
give_actor(actor, name) {
|
||||||
if (! actor.type.is_actor)
|
if (! actor.type.is_actor)
|
||||||
|
|||||||
@ -679,8 +679,10 @@ class Player extends PrimaryView {
|
|||||||
|
|
||||||
play_demo() {
|
play_demo() {
|
||||||
this.restart_level();
|
this.restart_level();
|
||||||
this.demo_faucet = this.level.stored_level.demo[Symbol.iterator]();
|
let demo = this.level.stored_level.demo;
|
||||||
this.level.force_floor_direction = this.level.stored_level.demo.initial_force_floor_direction;
|
this.demo_faucet = demo[Symbol.iterator]();
|
||||||
|
this.level.force_floor_direction = demo.initial_force_floor_direction;
|
||||||
|
this.level._blob_modifier = demo.blob_seed;
|
||||||
// FIXME should probably start playback on first real input
|
// FIXME should probably start playback on first real input
|
||||||
this.set_state('playing');
|
this.set_state('playing');
|
||||||
}
|
}
|
||||||
|
|||||||
@ -128,7 +128,9 @@ export class CanvasRenderer {
|
|||||||
// Draw one layer at a time, so animated objects aren't overdrawn by
|
// Draw one layer at a time, so animated objects aren't overdrawn by
|
||||||
// neighboring terrain
|
// neighboring terrain
|
||||||
// XXX layer count hardcoded here
|
// XXX layer count hardcoded here
|
||||||
for (let layer = 0; layer < 4; layer++) {
|
// FIXME this is a bit inefficient when there are a lot of rarely-used layers; consider
|
||||||
|
// instead drawing everything under actors, then actors, then everything above actors?
|
||||||
|
for (let layer = 0; layer < 5; layer++) {
|
||||||
for (let x = xf0; x <= x1; x++) {
|
for (let x = xf0; x <= x1; x++) {
|
||||||
for (let y = yf0; y <= y1; y++) {
|
for (let y = yf0; y <= y1; y++) {
|
||||||
for (let tile of this.level.cells[y][x]) {
|
for (let tile of this.level.cells[y][x]) {
|
||||||
|
|||||||
@ -98,7 +98,7 @@ export const CC2_TILESET_LAYOUT = {
|
|||||||
popdown_wall: [12, 5],
|
popdown_wall: [12, 5],
|
||||||
popdown_floor: [12, 5],
|
popdown_floor: [12, 5],
|
||||||
popdown_floor_visible: [13, 5],
|
popdown_floor_visible: [13, 5],
|
||||||
forbidden: [14, 5],
|
no_sign: [14, 5],
|
||||||
// TODO arrows overlay at [3, 10]
|
// TODO arrows overlay at [3, 10]
|
||||||
directional_block: [15, 5],
|
directional_block: [15, 5],
|
||||||
|
|
||||||
|
|||||||
@ -4,8 +4,9 @@ import { random_choice } from './util.js';
|
|||||||
// Draw layers
|
// Draw layers
|
||||||
const LAYER_TERRAIN = 0;
|
const LAYER_TERRAIN = 0;
|
||||||
const LAYER_ITEM = 1;
|
const LAYER_ITEM = 1;
|
||||||
const LAYER_ACTOR = 2;
|
const LAYER_NO_SIGN = 2;
|
||||||
const LAYER_OVERLAY = 3;
|
const LAYER_ACTOR = 3;
|
||||||
|
const LAYER_OVERLAY = 4;
|
||||||
// TODO cc2 order is: swivel, thinwalls, canopy (and yes you can have them all in the same tile)
|
// TODO cc2 order is: swivel, thinwalls, canopy (and yes you can have them all in the same tile)
|
||||||
|
|
||||||
function player_visual_state(me) {
|
function player_visual_state(me) {
|
||||||
@ -543,8 +544,19 @@ const TILE_TYPES = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
forbidden: {
|
no_sign: {
|
||||||
draw_layer: LAYER_TERRAIN,
|
draw_layer: LAYER_NO_SIGN,
|
||||||
|
disables_pickup: true,
|
||||||
|
blocks(me, level, other) {
|
||||||
|
let item;
|
||||||
|
for (let tile of me.cell) {
|
||||||
|
if (tile.type.is_item) {
|
||||||
|
item = tile.type.name;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return item && other.has_item(item);
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
// Mechanisms
|
// Mechanisms
|
||||||
@ -806,30 +818,42 @@ const TILE_TYPES = {
|
|||||||
},
|
},
|
||||||
teleport_blue: {
|
teleport_blue: {
|
||||||
draw_layer: LAYER_TERRAIN,
|
draw_layer: LAYER_TERRAIN,
|
||||||
teleport_dest_order(me, level) {
|
teleport_dest_order(me, level, other) {
|
||||||
return level.iter_tiles_in_reading_order(me.cell, 'teleport_blue', true);
|
return level.iter_tiles_in_reading_order(me.cell, 'teleport_blue', true);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
teleport_red: {
|
teleport_red: {
|
||||||
draw_layer: LAYER_TERRAIN,
|
draw_layer: LAYER_TERRAIN,
|
||||||
teleport_dest_order(me, level) {
|
teleport_try_all_directions: true,
|
||||||
// FIXME you can control your exit direction from red teleporters
|
teleport_allow_override: true,
|
||||||
|
teleport_dest_order(me, level, other) {
|
||||||
return level.iter_tiles_in_reading_order(me.cell, 'teleport_red');
|
return level.iter_tiles_in_reading_order(me.cell, 'teleport_red');
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
teleport_green: {
|
teleport_green: {
|
||||||
draw_layer: LAYER_TERRAIN,
|
draw_layer: LAYER_TERRAIN,
|
||||||
teleport_dest_order(me, level) {
|
teleport_try_all_directions: true,
|
||||||
// FIXME exit direction is random; unclear if it's any direction or only unblocked ones
|
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'));
|
||||||
// FIXME this should use the lynxish rng
|
if (all.length <= 1) {
|
||||||
return [random_choice(all), me];
|
// If this is the only teleporter, just walk out the other side — and, crucially, do
|
||||||
|
// NOT advance the PRNG
|
||||||
|
return [me];
|
||||||
|
}
|
||||||
|
// 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 list
|
||||||
|
let target = all[level.prng() % (all.length - 1)];
|
||||||
|
// Also set the actor's (initial) exit direction
|
||||||
|
level.set_actor_direction(other, ['north', 'east', 'south', 'west'][level.prng() % 4]);
|
||||||
|
return [target, me];
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
teleport_yellow: {
|
teleport_yellow: {
|
||||||
draw_layer: LAYER_TERRAIN,
|
draw_layer: LAYER_TERRAIN,
|
||||||
teleport_dest_order(me, level) {
|
teleport_allow_override: true,
|
||||||
// FIXME special pickup behavior
|
teleport_dest_order(me, level, other) {
|
||||||
|
// FIXME special pickup behavior; NOT an item though, does not combine with no sign
|
||||||
return level.iter_tiles_in_reading_order(me.cell, 'teleport_yellow', true);
|
return level.iter_tiles_in_reading_order(me.cell, 'teleport_yellow', true);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -1207,9 +1231,7 @@ const TILE_TYPES = {
|
|||||||
// TODO make this a... flag? i don't know?
|
// TODO make this a... flag? i don't know?
|
||||||
// TODO major difference from lynx...
|
// TODO major difference from lynx...
|
||||||
if (other.type.name !== 'ice_block' && other.type.name !== 'directional_block') {
|
if (other.type.name !== 'ice_block' && other.type.name !== 'directional_block') {
|
||||||
if (level.give_actor(other, me.type.name)) {
|
level.attempt_take(other, me);
|
||||||
level.remove_tile(me);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -1507,7 +1529,7 @@ for (let [name, type] of Object.entries(TILE_TYPES)) {
|
|||||||
|
|
||||||
if (type.draw_layer === undefined ||
|
if (type.draw_layer === undefined ||
|
||||||
type.draw_layer !== Math.floor(type.draw_layer) ||
|
type.draw_layer !== Math.floor(type.draw_layer) ||
|
||||||
type.draw_layer >= 4)
|
type.draw_layer >= 5)
|
||||||
{
|
{
|
||||||
console.error(`Tile type ${name} has a bad draw layer`);
|
console.error(`Tile type ${name} has a bad draw layer`);
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
Loading…
Reference in New Issue
Block a user