Change StoredCell to also be layered, and update the editor to match
This commit is contained in:
parent
323ed3ee18
commit
6fc4f6b58f
@ -1,6 +1,10 @@
|
|||||||
|
import { LAYERS } from './defs.js';
|
||||||
import * as util from './util.js';
|
import * as util from './util.js';
|
||||||
|
|
||||||
export class StoredCell extends Array {
|
export class StoredCell extends Array {
|
||||||
|
constructor() {
|
||||||
|
super(LAYERS.MAX);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class Replay {
|
export class Replay {
|
||||||
|
|||||||
@ -1073,10 +1073,12 @@ export function parse_level(buf, number = 1) {
|
|||||||
let mask = bytes[p];
|
let mask = bytes[p];
|
||||||
p++;
|
p++;
|
||||||
if (mask & 0x10) {
|
if (mask & 0x10) {
|
||||||
cell.push({type: TILE_TYPES['canopy']});
|
let type = TILE_TYPES['canopy'];
|
||||||
|
cell[type.layer] = {type};
|
||||||
}
|
}
|
||||||
if (mask & 0x0f) {
|
if (mask & 0x0f) {
|
||||||
cell.push({type: TILE_TYPES['thin_walls'], edges: mask & 0x0f});
|
let type = TILE_TYPES['thin_walls'];
|
||||||
|
cell[type.layer] = {type, edges: mask & 0x0f};
|
||||||
}
|
}
|
||||||
// Skip the rest of the loop. That means we don't handle any of the other
|
// Skip the rest of the loop. That means we don't handle any of the other
|
||||||
// special behavior below, but neither thin walls nor canopies should use
|
// special behavior below, but neither thin walls nor canopies should use
|
||||||
@ -1091,7 +1093,7 @@ export function parse_level(buf, number = 1) {
|
|||||||
let type = TILE_TYPES[name];
|
let type = TILE_TYPES[name];
|
||||||
if (!type) console.error(name, spec);
|
if (!type) console.error(name, spec);
|
||||||
let tile = {type};
|
let tile = {type};
|
||||||
cell.push(tile);
|
cell[type.layer] = tile;
|
||||||
if (spec.modifier) {
|
if (spec.modifier) {
|
||||||
spec.modifier.decode(tile, modifier);
|
spec.modifier.decode(tile, modifier);
|
||||||
}
|
}
|
||||||
@ -1100,7 +1102,8 @@ export function parse_level(buf, number = 1) {
|
|||||||
// TODO this should go on the bottom
|
// TODO this should go on the bottom
|
||||||
// TODO we should sort and also only allow one thing per layer
|
// TODO we should sort and also only allow one thing per layer
|
||||||
if (spec.dummy_terrain) {
|
if (spec.dummy_terrain) {
|
||||||
cell.push({type: TILE_TYPES[spec.dummy_terrain]});
|
let type = TILE_TYPES[spec.dummy_terrain];
|
||||||
|
cell[type.layer] = {type};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (type.is_required_chip) {
|
if (type.is_required_chip) {
|
||||||
@ -1125,7 +1128,6 @@ export function parse_level(buf, number = 1) {
|
|||||||
if (! spec.has_next)
|
if (! spec.has_next)
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
cell.reverse();
|
|
||||||
level.linear_cells.push(cell);
|
level.linear_cells.push(cell);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1389,15 +1391,12 @@ export function synthesize_level(stored_level) {
|
|||||||
map_view = new DataView(map_bytes.buffer);
|
map_view = new DataView(map_bytes.buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO complain if duplicates on a layer
|
|
||||||
let dummy_terrain_tile = null;
|
let dummy_terrain_tile = null;
|
||||||
let handled_thin_walls = false;
|
let handled_thin_walls = false;
|
||||||
// FIXME sort first, otherwise the canopy assumption that thin walls are immediately below
|
for (let i = LAYERS.MAX - 1; i >= 0; i--) {
|
||||||
// breaks! maybe just fix that also
|
|
||||||
for (let i = cell.length - 1; i >= 0; i--) {
|
|
||||||
let tile = cell[i];
|
let tile = cell[i];
|
||||||
// FIXME does not yet support canopy or thin walls >:S
|
if (! tile)
|
||||||
let spec = REVERSE_TILE_ENCODING[tile.type.name];
|
continue;
|
||||||
|
|
||||||
if (tile.type.name === 'canopy' || tile.type.name === 'thin_walls') {
|
if (tile.type.name === 'canopy' || tile.type.name === 'thin_walls') {
|
||||||
// These two tiles are encoded together despite being on different layers. If we
|
// These two tiles are encoded together despite being on different layers. If we
|
||||||
@ -1410,9 +1409,7 @@ export function synthesize_level(stored_level) {
|
|||||||
let canopy, thin_walls;
|
let canopy, thin_walls;
|
||||||
if (tile.type.name === 'canopy') {
|
if (tile.type.name === 'canopy') {
|
||||||
canopy = tile;
|
canopy = tile;
|
||||||
if (i > 0 && cell[i - 1].type.name === 'thin_walls') {
|
thin_walls = cell[LAYERS.thin_wall];
|
||||||
thin_walls = cell[i - 1];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
thin_walls = tile;
|
thin_walls = tile;
|
||||||
@ -1432,6 +1429,8 @@ export function synthesize_level(stored_level) {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let spec = REVERSE_TILE_ENCODING[tile.type.name];
|
||||||
|
|
||||||
// Handle the swivel, a tile that draws as an overlay but is stored like terrain. In a
|
// Handle the swivel, a tile that draws as an overlay but is stored like terrain. In a
|
||||||
// level, it has two parts: the swivel itself, and a dummy swivel_floor terrain which is
|
// level, it has two parts: the swivel itself, and a dummy swivel_floor terrain which is
|
||||||
// unencodable. To encode that, we notice when we hit a swivel (which happens first),
|
// unencodable. To encode that, we notice when we hit a swivel (which happens first),
|
||||||
|
|||||||
@ -250,14 +250,15 @@ function parse_level(bytes, number) {
|
|||||||
// pgchip grants directions to ice blocks on cloners by putting a clone block
|
// pgchip grants directions to ice blocks on cloners by putting a clone block
|
||||||
// beneath them instead
|
// beneath them instead
|
||||||
if (l === 1 && 0x0e <= tile_byte && tile_byte <= 0x11 &&
|
if (l === 1 && 0x0e <= tile_byte && tile_byte <= 0x11 &&
|
||||||
cell[0] && cell[0].type.name === 'ice_block')
|
cell[LAYER.actor] && cell[LAYER.actor].type.name === 'ice_block')
|
||||||
{
|
{
|
||||||
cell[0].direction = extra.direction;
|
cell[LAYER.actor].direction = extra.direction;
|
||||||
cell.unshift({type: TILE_TYPES['cloner']});
|
let type = TILE_TYPES['cloner'];
|
||||||
|
cell[type.layer] = {type};
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
cell.unshift({...tile});
|
cell[tile.type.layer] = {...tile};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (c !== 1024)
|
if (c !== 1024)
|
||||||
@ -266,9 +267,8 @@ function parse_level(bytes, number) {
|
|||||||
|
|
||||||
// Fix the "floor/empty" nonsense here by adding floor to any cell with no terrain on bottom
|
// Fix the "floor/empty" nonsense here by adding floor to any cell with no terrain on bottom
|
||||||
for (let cell of level.linear_cells) {
|
for (let cell of level.linear_cells) {
|
||||||
if (cell.length === 0 || cell[0].type.layer !== LAYERS.terrain) {
|
if (! cell[LAYER.terrain]) {
|
||||||
// No terrain; insert a floor
|
cell[LAYER.terrain] = { type: TILE_TYPES['floor'] };
|
||||||
cell.unshift({ type: TILE_TYPES['floor'] });
|
|
||||||
}
|
}
|
||||||
// TODO we could also deal with weird cases where there's terrain /on top of/ something
|
// TODO we could also deal with weird cases where there's terrain /on top of/ something
|
||||||
// else: things underwater, the quirk where a glider will erase the item underneath...
|
// else: things underwater, the quirk where a glider will erase the item underneath...
|
||||||
|
|||||||
@ -453,9 +453,10 @@ export class Level extends LevelInterface {
|
|||||||
|
|
||||||
let stored_cell = this.stored_level.linear_cells[n];
|
let stored_cell = this.stored_level.linear_cells[n];
|
||||||
n++;
|
n++;
|
||||||
|
|
||||||
// FIXME give this same treatment to stored cells (otherwise the editor is fucked)
|
|
||||||
for (let template_tile of stored_cell) {
|
for (let template_tile of stored_cell) {
|
||||||
|
if (! template_tile)
|
||||||
|
continue;
|
||||||
|
|
||||||
let tile = Tile.from_template(template_tile);
|
let tile = Tile.from_template(template_tile);
|
||||||
if (tile.type.is_hint) {
|
if (tile.type.is_hint) {
|
||||||
// Copy over the tile-specific hint, if any
|
// Copy over the tile-specific hint, if any
|
||||||
|
|||||||
@ -444,7 +444,13 @@ class PencilOperation extends DrawOperation {
|
|||||||
if (this.modifier === 'ctrl') {
|
if (this.modifier === 'ctrl') {
|
||||||
let cell = this.cell(x, y);
|
let cell = this.cell(x, y);
|
||||||
if (cell) {
|
if (cell) {
|
||||||
this.editor.select_palette(cell[cell.length - 1]);
|
// Pick the topmost thing
|
||||||
|
for (let layer = LAYERS.MAX - 1; layer >= 0; layer--) {
|
||||||
|
if (cell[layer]) {
|
||||||
|
this.editor.select_palette(cell[layer]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -455,8 +461,10 @@ class PencilOperation extends DrawOperation {
|
|||||||
let cell = this.cell(x, y);
|
let cell = this.cell(x, y);
|
||||||
if (this.modifier === 'shift') {
|
if (this.modifier === 'shift') {
|
||||||
// Aggressive mode: erase the entire cell
|
// Aggressive mode: erase the entire cell
|
||||||
cell.length = 0;
|
for (let layer = 0; layer < LAYERS.MAX; layer++) {
|
||||||
cell.push({type: TILE_TYPES.floor});
|
cell[layer] = null;
|
||||||
|
}
|
||||||
|
cell[LAYERS.terrain] = {type: TILE_TYPES.floor};
|
||||||
}
|
}
|
||||||
else if (template) {
|
else if (template) {
|
||||||
// Erase whatever's on the same layer
|
// Erase whatever's on the same layer
|
||||||
@ -470,10 +478,12 @@ class PencilOperation extends DrawOperation {
|
|||||||
if (this.modifier === 'shift') {
|
if (this.modifier === 'shift') {
|
||||||
// Aggressive mode: erase whatever's already in the cell
|
// Aggressive mode: erase whatever's already in the cell
|
||||||
let cell = this.cell(x, y);
|
let cell = this.cell(x, y);
|
||||||
cell.length = 0;
|
for (let layer = 0; layer < LAYERS.MAX; layer++) {
|
||||||
|
cell[layer] = null;
|
||||||
|
}
|
||||||
let type = this.editor.palette_selection.type;
|
let type = this.editor.palette_selection.type;
|
||||||
if (type.layer !== LAYERS.terrain) {
|
if (type.layer !== LAYERS.terrain) {
|
||||||
cell.push({type: TILE_TYPES.floor});
|
cell[LAYERS.terrain] = {type: TILE_TYPES.floor};
|
||||||
}
|
}
|
||||||
this.editor.place_in_cell(x, y, template);
|
this.editor.place_in_cell(x, y, template);
|
||||||
}
|
}
|
||||||
@ -593,16 +603,16 @@ class ForceFloorOperation extends DrawOperation {
|
|||||||
// had some kind of force floor
|
// had some kind of force floor
|
||||||
if (i === 2) {
|
if (i === 2) {
|
||||||
let prevcell = this.editor.cell(prevx, prevy);
|
let prevcell = this.editor.cell(prevx, prevy);
|
||||||
if (prevcell[0].type.name.startsWith('force_floor_')) {
|
if (prevcell[LAYERS.terrain].type.name.startsWith('force_floor_')) {
|
||||||
prevcell[0].type = TILE_TYPES[name];
|
prevcell[LAYERS.terrain].type = TILE_TYPES[name];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Drawing a loop with force floors creates ice (but not in the previous
|
// Drawing a loop with force floors creates ice (but not in the previous
|
||||||
// cell, obviously)
|
// cell, obviously)
|
||||||
let cell = this.editor.cell(x, y);
|
let cell = this.editor.cell(x, y);
|
||||||
if (cell[0].type.name.startsWith('force_floor_') &&
|
if (cell[LAYERS.terrain].type.name.startsWith('force_floor_') &&
|
||||||
cell[0].type.name !== name)
|
cell[LAYERS.terrain].type.name !== name)
|
||||||
{
|
{
|
||||||
name = 'ice';
|
name = 'ice';
|
||||||
}
|
}
|
||||||
@ -732,9 +742,8 @@ class WireOperation extends DrawOperation {
|
|||||||
}
|
}
|
||||||
let bit = DIRECTIONS[direction].bit;
|
let bit = DIRECTIONS[direction].bit;
|
||||||
|
|
||||||
for (let tile of cell) {
|
let terrain = cell[LAYERS.terrain];
|
||||||
if (tile.type.name !== 'floor')
|
if (terrain.type.name === 'floor') {
|
||||||
continue;
|
|
||||||
if (this.alt_mode) {
|
if (this.alt_mode) {
|
||||||
tile.wire_tunnel_directions &= ~bit;
|
tile.wire_tunnel_directions &= ~bit;
|
||||||
}
|
}
|
||||||
@ -846,6 +855,8 @@ class WireOperation extends DrawOperation {
|
|||||||
let cell = this.cell(x, y);
|
let cell = this.cell(x, y);
|
||||||
for (let tile of Array.from(cell).reverse()) {
|
for (let tile of Array.from(cell).reverse()) {
|
||||||
// TODO probably a better way to do this
|
// TODO probably a better way to do this
|
||||||
|
if (! tile)
|
||||||
|
continue;
|
||||||
if (['floor', 'steel', 'button_pink', 'button_black', 'teleport_blue', 'teleport_red', 'light_switch_on', 'light_switch_off', 'circuit_block'].indexOf(tile.type.name) < 0)
|
if (['floor', 'steel', 'button_pink', 'button_black', 'teleport_blue', 'teleport_red', 'light_switch_on', 'light_switch_off', 'circuit_block'].indexOf(tile.type.name) < 0)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
@ -908,7 +919,7 @@ class AdjustOperation extends MouseOperation {
|
|||||||
let cell = this.cell(this.gx1, this.gy1);
|
let cell = this.cell(this.gx1, this.gy1);
|
||||||
if (this.modifier === 'ctrl') {
|
if (this.modifier === 'ctrl') {
|
||||||
for (let tile of cell) {
|
for (let tile of cell) {
|
||||||
if (TILES_WITH_PROPS[tile.type.name] !== undefined) {
|
if (tile && TILES_WITH_PROPS[tile.type.name] !== undefined) {
|
||||||
// TODO use the tile's bbox, not the mouse position
|
// TODO use the tile's bbox, not the mouse position
|
||||||
this.editor.open_tile_prop_overlay(tile, this.mx0, this.my0);
|
this.editor.open_tile_prop_overlay(tile, this.mx0, this.my0);
|
||||||
break;
|
break;
|
||||||
@ -917,8 +928,10 @@ class AdjustOperation extends MouseOperation {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// FIXME implement shift to always target floor, or maybe start from bottom
|
// FIXME implement shift to always target floor, or maybe start from bottom
|
||||||
for (let i = cell.length - 1; i >= 0; i--) {
|
for (let layer = LAYERS.MAX - 1; layer >= 0; layer--) {
|
||||||
let tile = cell[i];
|
let tile = cell[layer];
|
||||||
|
if (! tile)
|
||||||
|
continue;
|
||||||
|
|
||||||
let rotated;
|
let rotated;
|
||||||
if (this.alt_mode) {
|
if (this.alt_mode) {
|
||||||
@ -2301,7 +2314,7 @@ class Selection {
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
this.floated_cells.push(stored_level.linear_cells[n]);
|
this.floated_cells.push(stored_level.linear_cells[n]);
|
||||||
stored_level.linear_cells[n] = [{type: TILE_TYPES['floor']}];
|
stored_level.linear_cells[n] = this.editor._make_cell(x, y);
|
||||||
this.editor.mark_cell_dirty(stored_level.linear_cells[n]);
|
this.editor.mark_cell_dirty(stored_level.linear_cells[n]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2712,9 +2725,11 @@ export class Editor extends PrimaryView {
|
|||||||
|
|
||||||
// Level creation, management, and saving
|
// Level creation, management, and saving
|
||||||
|
|
||||||
_make_cell() {
|
_make_cell(x, y) {
|
||||||
let cell = new format_base.StoredCell;
|
let cell = new format_base.StoredCell;
|
||||||
cell.push({type: TILE_TYPES['floor']});
|
cell.x = x;
|
||||||
|
cell.y = y;
|
||||||
|
cell[LAYERS.terrain] = {type: TILE_TYPES.floor};
|
||||||
return cell;
|
return cell;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2725,9 +2740,9 @@ export class Editor extends PrimaryView {
|
|||||||
stored_level.size_y = size_y;
|
stored_level.size_y = size_y;
|
||||||
stored_level.viewport_size = 10;
|
stored_level.viewport_size = 10;
|
||||||
for (let i = 0; i < size_x * size_y; i++) {
|
for (let i = 0; i < size_x * size_y; i++) {
|
||||||
stored_level.linear_cells.push(this._make_cell());
|
stored_level.linear_cells.push(this._make_cell(...stored_level.scalar_to_coords(i)));
|
||||||
}
|
}
|
||||||
stored_level.linear_cells[0].push({type: TILE_TYPES['player'], direction: 'south'});
|
stored_level.linear_cells[LAYERS.actor] = {type: TILE_TYPES['player'], direction: 'south'};
|
||||||
return stored_level;
|
return stored_level;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3098,26 +3113,25 @@ export class Editor extends PrimaryView {
|
|||||||
let cell = this.cell(x, y);
|
let cell = this.cell(x, y);
|
||||||
// Replace whatever's on the same layer
|
// Replace whatever's on the same layer
|
||||||
// TODO should preserve wiring if possible too
|
// TODO should preserve wiring if possible too
|
||||||
for (let i = cell.length - 1; i >= 0; i--) {
|
let existing_tile = cell[tile.type.layer];
|
||||||
|
if (existing_tile) {
|
||||||
// If we find a tile of the same type as the one being drawn, see if it has custom
|
// If we find a tile of the same type as the one being drawn, see if it has custom
|
||||||
// combine behavior (only the case if it came from the palette)
|
// combine behavior (only the case if it came from the palette)
|
||||||
if (cell[i].type === tile.type &&
|
if (existing_tile.type === tile.type &&
|
||||||
// FIXME this is hacky garbage
|
// FIXME this is hacky garbage
|
||||||
tile === this.palette_selection && this.palette_selection_from_palette &&
|
tile === this.palette_selection && this.palette_selection_from_palette &&
|
||||||
SPECIAL_PALETTE_BEHAVIOR[tile.type.name] &&
|
SPECIAL_PALETTE_BEHAVIOR[tile.type.name] &&
|
||||||
SPECIAL_PALETTE_BEHAVIOR[tile.type.name].combine_draw)
|
SPECIAL_PALETTE_BEHAVIOR[tile.type.name].combine_draw)
|
||||||
{
|
{
|
||||||
SPECIAL_PALETTE_BEHAVIOR[tile.type.name].combine_draw(tile, cell[i]);
|
SPECIAL_PALETTE_BEHAVIOR[tile.type.name].combine_draw(tile, existing_tile);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cell[i].type.layer === tile.type.layer) {
|
// Otherwise erase it
|
||||||
cell.splice(i, 1);
|
cell[tile.type.layer] = null;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
cell.push(Object.assign({}, tile));
|
cell[tile.type.layer] = {...tile};
|
||||||
cell.sort((a, b) => a.type.layer - b.type.layer);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
erase_tile(cell, tile = null) {
|
erase_tile(cell, tile = null) {
|
||||||
@ -3127,29 +3141,28 @@ export class Editor extends PrimaryView {
|
|||||||
tile = this.palette_selection;
|
tile = this.palette_selection;
|
||||||
}
|
}
|
||||||
|
|
||||||
let layer = tile.type.layer;
|
let existing_tile = cell[tile.type.layer];
|
||||||
for (let i = cell.length - 1; i >= 0; i--) {
|
if (existing_tile) {
|
||||||
// If we find a tile of the same type as the one being drawn, see if it has custom
|
// If we find a tile of the same type as the one being drawn, see if it has custom
|
||||||
// combine behavior (only the case if it came from the palette)
|
// combine behavior (only the case if it came from the palette)
|
||||||
if (cell[i].type === tile.type &&
|
if (existing_tile.type === tile.type &&
|
||||||
// FIXME this is hacky garbage
|
// FIXME this is hacky garbage
|
||||||
tile === this.palette_selection && this.palette_selection_from_palette &&
|
tile === this.palette_selection && this.palette_selection_from_palette &&
|
||||||
SPECIAL_PALETTE_BEHAVIOR[tile.type.name] &&
|
SPECIAL_PALETTE_BEHAVIOR[tile.type.name] &&
|
||||||
SPECIAL_PALETTE_BEHAVIOR[tile.type.name].combine_erase)
|
SPECIAL_PALETTE_BEHAVIOR[tile.type.name].combine_erase)
|
||||||
{
|
{
|
||||||
let remove = SPECIAL_PALETTE_BEHAVIOR[tile.type.name].combine_erase(tile, cell[i]);
|
let remove = SPECIAL_PALETTE_BEHAVIOR[tile.type.name].combine_erase(tile, existing_tile);
|
||||||
if (! remove)
|
if (! remove)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cell[i].type.layer === layer) {
|
// Otherwise erase it
|
||||||
cell.splice(i, 1);
|
cell[tile.type.layer] = null;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Don't allow erasing the floor entirely
|
// Don't allow erasing the floor entirely
|
||||||
if (layer === 0) {
|
if (tile.type.layer === LAYERS.terrain) {
|
||||||
cell.unshift({type: TILE_TYPES.floor});
|
cell[LAYERS.terrain] = {type: TILE_TYPES.floor};
|
||||||
}
|
}
|
||||||
this.mark_cell_dirty(cell);
|
this.mark_cell_dirty(cell);
|
||||||
}
|
}
|
||||||
@ -3201,7 +3214,7 @@ export class Editor extends PrimaryView {
|
|||||||
let new_cells = [];
|
let new_cells = [];
|
||||||
for (let y = y0; y < y0 + size_y; y++) {
|
for (let y = y0; y < y0 + size_y; y++) {
|
||||||
for (let x = x0; x < x0 + size_x; x++) {
|
for (let x = x0; x < x0 + size_x; x++) {
|
||||||
new_cells.push(this.cell(x, y) ?? this._make_cell());
|
new_cells.push(this.cell(x, y) ?? this._make_cell(x, y));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user