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';
export class StoredCell extends Array {
constructor() {
super(LAYERS.MAX);
}
}
export class Replay {

View File

@ -1073,10 +1073,12 @@ export function parse_level(buf, number = 1) {
let mask = bytes[p];
p++;
if (mask & 0x10) {
cell.push({type: TILE_TYPES['canopy']});
let type = TILE_TYPES['canopy'];
cell[type.layer] = {type};
}
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
// 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];
if (!type) console.error(name, spec);
let tile = {type};
cell.push(tile);
cell[type.layer] = tile;
if (spec.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 we should sort and also only allow one thing per layer
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) {
@ -1125,7 +1128,6 @@ export function parse_level(buf, number = 1) {
if (! spec.has_next)
break;
}
cell.reverse();
level.linear_cells.push(cell);
}
}
@ -1389,15 +1391,12 @@ export function synthesize_level(stored_level) {
map_view = new DataView(map_bytes.buffer);
}
// TODO complain if duplicates on a layer
let dummy_terrain_tile = null;
let handled_thin_walls = false;
// FIXME sort first, otherwise the canopy assumption that thin walls are immediately below
// breaks! maybe just fix that also
for (let i = cell.length - 1; i >= 0; i--) {
for (let i = LAYERS.MAX - 1; i >= 0; i--) {
let tile = cell[i];
// FIXME does not yet support canopy or thin walls >:S
let spec = REVERSE_TILE_ENCODING[tile.type.name];
if (! tile)
continue;
if (tile.type.name === 'canopy' || tile.type.name === 'thin_walls') {
// 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;
if (tile.type.name === 'canopy') {
canopy = tile;
if (i > 0 && cell[i - 1].type.name === 'thin_walls') {
thin_walls = cell[i - 1];
}
thin_walls = cell[LAYERS.thin_wall];
}
else {
thin_walls = tile;
@ -1432,6 +1429,8 @@ export function synthesize_level(stored_level) {
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
// 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),

View File

@ -250,14 +250,15 @@ function parse_level(bytes, number) {
// pgchip grants directions to ice blocks on cloners by putting a clone block
// beneath them instead
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.unshift({type: TILE_TYPES['cloner']});
cell[LAYER.actor].direction = extra.direction;
let type = TILE_TYPES['cloner'];
cell[type.layer] = {type};
continue;
}
cell.unshift({...tile});
cell[tile.type.layer] = {...tile};
}
}
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
for (let cell of level.linear_cells) {
if (cell.length === 0 || cell[0].type.layer !== LAYERS.terrain) {
// No terrain; insert a floor
cell.unshift({ type: TILE_TYPES['floor'] });
if (! cell[LAYER.terrain]) {
cell[LAYER.terrain] = { type: TILE_TYPES['floor'] };
}
// 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...

View File

@ -453,9 +453,10 @@ export class Level extends LevelInterface {
let stored_cell = this.stored_level.linear_cells[n];
n++;
// FIXME give this same treatment to stored cells (otherwise the editor is fucked)
for (let template_tile of stored_cell) {
if (! template_tile)
continue;
let tile = Tile.from_template(template_tile);
if (tile.type.is_hint) {
// Copy over the tile-specific hint, if any

View File

@ -444,7 +444,13 @@ class PencilOperation extends DrawOperation {
if (this.modifier === 'ctrl') {
let cell = this.cell(x, y);
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;
}
@ -455,8 +461,10 @@ class PencilOperation extends DrawOperation {
let cell = this.cell(x, y);
if (this.modifier === 'shift') {
// Aggressive mode: erase the entire cell
cell.length = 0;
cell.push({type: TILE_TYPES.floor});
for (let layer = 0; layer < LAYERS.MAX; layer++) {
cell[layer] = null;
}
cell[LAYERS.terrain] = {type: TILE_TYPES.floor};
}
else if (template) {
// Erase whatever's on the same layer
@ -470,10 +478,12 @@ class PencilOperation extends DrawOperation {
if (this.modifier === 'shift') {
// Aggressive mode: erase whatever's already in the cell
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;
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);
}
@ -593,16 +603,16 @@ class ForceFloorOperation extends DrawOperation {
// had some kind of force floor
if (i === 2) {
let prevcell = this.editor.cell(prevx, prevy);
if (prevcell[0].type.name.startsWith('force_floor_')) {
prevcell[0].type = TILE_TYPES[name];
if (prevcell[LAYERS.terrain].type.name.startsWith('force_floor_')) {
prevcell[LAYERS.terrain].type = TILE_TYPES[name];
}
}
// Drawing a loop with force floors creates ice (but not in the previous
// cell, obviously)
let cell = this.editor.cell(x, y);
if (cell[0].type.name.startsWith('force_floor_') &&
cell[0].type.name !== name)
if (cell[LAYERS.terrain].type.name.startsWith('force_floor_') &&
cell[LAYERS.terrain].type.name !== name)
{
name = 'ice';
}
@ -732,9 +742,8 @@ class WireOperation extends DrawOperation {
}
let bit = DIRECTIONS[direction].bit;
for (let tile of cell) {
if (tile.type.name !== 'floor')
continue;
let terrain = cell[LAYERS.terrain];
if (terrain.type.name === 'floor') {
if (this.alt_mode) {
tile.wire_tunnel_directions &= ~bit;
}
@ -846,6 +855,8 @@ class WireOperation extends DrawOperation {
let cell = this.cell(x, y);
for (let tile of Array.from(cell).reverse()) {
// 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)
continue;
@ -908,7 +919,7 @@ class AdjustOperation extends MouseOperation {
let cell = this.cell(this.gx1, this.gy1);
if (this.modifier === 'ctrl') {
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
this.editor.open_tile_prop_overlay(tile, this.mx0, this.my0);
break;
@ -917,8 +928,10 @@ class AdjustOperation extends MouseOperation {
return;
}
// FIXME implement shift to always target floor, or maybe start from bottom
for (let i = cell.length - 1; i >= 0; i--) {
let tile = cell[i];
for (let layer = LAYERS.MAX - 1; layer >= 0; layer--) {
let tile = cell[layer];
if (! tile)
continue;
let rotated;
if (this.alt_mode) {
@ -2301,7 +2314,7 @@ class Selection {
}
else {
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]);
}
}
@ -2712,9 +2725,11 @@ export class Editor extends PrimaryView {
// Level creation, management, and saving
_make_cell() {
_make_cell(x, y) {
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;
}
@ -2725,9 +2740,9 @@ export class Editor extends PrimaryView {
stored_level.size_y = size_y;
stored_level.viewport_size = 10;
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;
}
@ -3098,26 +3113,25 @@ export class Editor extends PrimaryView {
let cell = this.cell(x, y);
// Replace whatever's on the same layer
// 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
// 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
tile === this.palette_selection && this.palette_selection_from_palette &&
SPECIAL_PALETTE_BEHAVIOR[tile.type.name] &&
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;
}
if (cell[i].type.layer === tile.type.layer) {
cell.splice(i, 1);
}
// Otherwise erase it
cell[tile.type.layer] = null;
}
cell.push(Object.assign({}, tile));
cell.sort((a, b) => a.type.layer - b.type.layer);
cell[tile.type.layer] = {...tile};
}
erase_tile(cell, tile = null) {
@ -3127,29 +3141,28 @@ export class Editor extends PrimaryView {
tile = this.palette_selection;
}
let layer = tile.type.layer;
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
// 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
tile === this.palette_selection && this.palette_selection_from_palette &&
SPECIAL_PALETTE_BEHAVIOR[tile.type.name] &&
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)
return;
}
if (cell[i].type.layer === layer) {
cell.splice(i, 1);
}
// Otherwise erase it
cell[tile.type.layer] = null;
}
// Don't allow erasing the floor entirely
if (layer === 0) {
cell.unshift({type: TILE_TYPES.floor});
if (tile.type.layer === LAYERS.terrain) {
cell[LAYERS.terrain] = {type: TILE_TYPES.floor};
}
this.mark_cell_dirty(cell);
}
@ -3201,7 +3214,7 @@ export class Editor extends PrimaryView {
let new_cells = [];
for (let y = y0; y < y0 + size_y; y++) {
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));
}
}