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';
|
||||
|
||||
export class StoredCell extends Array {
|
||||
constructor() {
|
||||
super(LAYERS.MAX);
|
||||
}
|
||||
}
|
||||
|
||||
export class Replay {
|
||||
|
||||
@ -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),
|
||||
|
||||
@ -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...
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user