Add support for encoding thin walls/canopies; add them to the editor; add support for additive drawing

This commit is contained in:
Eevee (Evelyn Woods) 2020-12-29 20:12:54 -07:00
parent b9a311a18c
commit 746300a514
2 changed files with 173 additions and 22 deletions

View File

@ -1391,11 +1391,47 @@ export function synthesize_level(stored_level) {
// 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--) {
let tile = cell[i];
// FIXME does not yet support canopy or thin walls >:S
let spec = REVERSE_TILE_ENCODING[tile.type.name];
if (tile.type.name === 'canopy' || tile.type.name === 'thin_walls') {
// These two tiles are encoded together despite being on different layers. If we
// see the canopy first, then find the thin wall tile (if any) and set a flag so we
// don't try to encode it again
if (handled_thin_walls)
continue;
handled_thin_walls = true;
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];
}
}
else {
thin_walls = tile;
}
let arg = 0;
if (canopy) {
arg |= 0x10;
}
if (thin_walls) {
arg |= thin_walls.edges;
}
map_bytes[p] = REVERSE_TILE_ENCODING['#thinwall/canopy'].tile_byte;
map_bytes[p + 1] = arg;
p += 2;
continue;
}
// 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

@ -423,18 +423,7 @@ class PencilOperation extends DrawOperation {
}
else if (template) {
// Erase whatever's on the same layer
// TODO this seems like the wrong place for this
let layer = template.type.draw_layer;
for (let i = cell.length - 1; i >= 0; i--) {
if (cell[i].type.draw_layer === layer) {
cell.splice(i, 1);
}
}
// Don't allow erasing the floor entirely
if (layer === 0) {
cell.unshift({type: TILE_TYPES.floor});
}
this.editor.mark_cell_dirty(cell);
this.editor.erase_tile(cell);
}
}
else {
@ -1124,9 +1113,9 @@ const EDITOR_TOOL_ORDER = ['pencil', 'adjust', 'force-floors', 'tracks', 'wire',
const EDITOR_PALETTE = [{
title: "Basics",
tiles: [
'player', 'player2',
'chip', 'chip_extra',
'floor', 'wall', 'hint', 'socket', 'exit',
'player', 'player2', 'hint',
'floor', 'wall', 'thin_walls/south',
'chip', 'chip_extra', 'socket', 'exit',
],
}, {
title: "Terrain",
@ -1161,6 +1150,7 @@ const EDITOR_PALETTE = [{
'water', 'turtle', 'fire',
'ice', 'ice_nw',
'force_floor_n', 'force_floor_all',
'canopy',
],
}, {
title: "Items",
@ -1266,13 +1256,14 @@ const EDITOR_PALETTE = [{
}];
const SPECIAL_PALETTE_ENTRIES = {
'thin_walls/south': { name: 'thin_walls', edges: DIRECTIONS['south'].bit },
'frame_block/0': { name: 'frame_block', direction: 'south', arrows: new Set },
'frame_block/1': { name: 'frame_block', direction: 'north', arrows: new Set(['north']) },
'frame_block/2a': { name: 'frame_block', direction: 'north', arrows: new Set(['north', 'east']) },
'frame_block/2o': { name: 'frame_block', direction: 'south', arrows: new Set(['north', 'south']) },
'frame_block/3': { name: 'frame_block', direction: 'south', arrows: new Set(['north', 'east', 'south']) },
'frame_block/4': { name: 'frame_block', direction: 'south', arrows: new Set(['north', 'east', 'south', 'west']) },
// FIXME these should be additive/subtractive, but a track picked up from the level should replace
// FIXME need to handle entered_direction intelligently, but also allow setting it explicitly
'railroad/straight': { name: 'railroad', tracks: 1 << 5, track_switch: null, entered_direction: 'north' },
'railroad/curve': { name: 'railroad', tracks: 1 << 0, track_switch: null, entered_direction: 'north' },
'railroad/switch': { name: 'railroad', tracks: 0, track_switch: 0, entered_direction: 'north' },
@ -1289,6 +1280,31 @@ const SPECIAL_PALETTE_ENTRIES = {
const _RAILROAD_ROTATED_LEFT = [3, 0, 1, 2, 5, 4];
const _RAILROAD_ROTATED_RIGHT = [1, 2, 3, 0, 5, 4];
const SPECIAL_PALETTE_BEHAVIOR = {
thin_walls: {
pick_palette_entry(tile) {
return 'thin_walls/south';
},
rotate_left(tile) {
if (tile.edges & 0x01) {
tile.edges |= 0x10;
}
tile.edges >>= 1;
},
rotate_right(tile) {
tile.edges <<= 1;
if (tile.edges & 0x10) {
tile.edges = (tile.edges & ~0x10) | 0x01;
}
},
combine_draw(palette_tile, existing_tile) {
existing_tile.edges |= palette_tile.edges;
},
combine_erase(palette_tile, existing_tile) {
existing_tile.edges &= ~palette_tile.edges;
if (existing_tile.edges === 0)
return true;
},
},
frame_block: {
pick_palette_entry(tile) {
if (tile.arrows.size === 2) {
@ -1378,6 +1394,61 @@ const SPECIAL_PALETTE_BEHAVIOR = {
tile.entered_direction = DIRECTIONS[tile.entered_direction].right;
}
},
combine_draw(palette_tile, existing_tile) {
existing_tile.tracks |= palette_tile.tracks;
// If we have a switch already, the just-placed track becomes the current one
if (existing_tile.track_switch !== null) {
for (let i = 0; i < 6; i++) {
if (palette_tile.tracks & (1 << i)) {
existing_tile.track_switch = i;
break;
}
}
}
if (palette_tile.track_switch !== null && existing_tile.track_switch === null) {
// The palette's switch is just an indication that we should have one, not what it
// ought to be
existing_tile.track_switch = palette_tile.track_switch;
for (let i = 0; i < 6; i++) {
if (existing_tile.tracks & (1 << i)) {
existing_tile.track_switch = i;
break;
}
}
}
},
combine_erase(palette_tile, existing_tile) {
existing_tile.tracks &= ~palette_tile.tracks;
// If there are no track pieces left, remove the railroad. It's technically possible to
// have a railroad with no tracks, but you're very unlikely to want it, and if you
// really do then you can do it yourself
if (existing_tile.tracks === 0)
return true;
if (palette_tile.track_switch !== null) {
existing_tile.track_switch = null;
}
// Fix the track switch if necessary
if (existing_tile.track_switch !== null) {
let num_tracks = 0;
for (let i = 0; i < 6; i++) {
if (existing_tile.tracks & (1 << i)) {
num_tracks += 1;
if (! (existing_tile.tracks & (1 << existing_tile.track_switch))) {
existing_tile.track_switch = i;
}
}
}
// Remove the switch if there's nothing to switch
if (num_tracks <= 1) {
existing_tile.track_switch = null;
}
}
},
},
circuit_block: {
pick_palette_entry(tile) {
@ -1693,15 +1764,15 @@ export class Editor extends PrimaryView {
let tile = Object.assign({}, SPECIAL_PALETTE_ENTRIES[key]);
tile.type = TILE_TYPES[tile.name];
delete tile.name;
this.select_palette(tile);
this.select_palette(tile, true);
}
else {
// Regular tile name
this.select_palette(key);
this.select_palette(key, true);
}
});
this.palette_selection = null;
this.select_palette('floor');
this.select_palette('floor', true);
}
activate() {
@ -1911,7 +1982,7 @@ export class Editor extends PrimaryView {
this.tool_button_els[this.current_tool].classList.add('-selected');
}
select_palette(name_or_tile) {
select_palette(name_or_tile, from_palette = false) {
let name, tile;
if (typeof name_or_tile === 'string') {
name = name_or_tile;
@ -1936,6 +2007,7 @@ export class Editor extends PrimaryView {
// Store the tile
this.palette_selection = tile;
this.palette_selection_from_palette = from_palette;
// Select it in the palette, if possible
let key = name;
@ -2028,18 +2100,61 @@ export class Editor extends PrimaryView {
let cell = this.cell(x, y);
// Replace whatever's on the same layer
// TODO probably not the best heuristic yet, since i imagine you can
// combine e.g. the tent with thin walls
// TODO should preserve wiring if possible too
for (let i = cell.length - 1; i >= 0; i--) {
// 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 &&
// 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]);
return;
}
if (cell[i].type.draw_layer === tile.type.draw_layer) {
cell.splice(i, 1);
}
}
cell.push(Object.assign({}, tile));
cell.sort((a, b) => a.type.draw_layer - b.type.draw_layer);
}
erase_tile(cell, tile = null) {
if (tile === null) {
tile = this.palette_selection;
}
let layer = tile.type.draw_layer;
for (let i = cell.length - 1; i >= 0; i--) {
// 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 &&
// 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]);
if (! remove)
return;
}
if (cell[i].type.draw_layer === layer) {
cell.splice(i, 1);
}
}
// Don't allow erasing the floor entirely
if (layer === 0) {
cell.unshift({type: TILE_TYPES.floor});
}
this.mark_cell_dirty(cell);
}
open_tile_prop_overlay(tile, x0, y0) {
this.cancel_mouse_operation();
// FIXME keep these around, don't recreate them constantly