Change StoredCell to also be layered, and update the editor to match

This commit is contained in:
Eevee (Evelyn Woods) 2021-01-03 17:44:16 -07:00
parent 323ed3ee18
commit 6fc4f6b58f
5 changed files with 78 additions and 61 deletions

View File

@ -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 {

View File

@ -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),

View File

@ -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...

View File

@ -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

View File

@ -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));
} }
} }