diff --git a/js/tileset.js b/js/tileset.js index 7958426..4b6de86 100644 --- a/js/tileset.js +++ b/js/tileset.js @@ -32,7 +32,7 @@ export const CC2_TILESET_LAYOUT = { key_yellow: [6, 1], key_green: [7, 1], dirt_block: { - special: 'perception', + __special__: 'perception', modes: new Set(['editor', 'xray']), hidden: [8, 1], revealed: [9, 1], @@ -46,13 +46,14 @@ export const CC2_TILESET_LAYOUT = { floor: { // Wiring! + __special__: 'wires', base: [0, 2], wired: [8, 26], wired_cross: [10, 26], is_wired_optional: true, }, wall_invisible: { - special: 'perception', + __special__: 'perception', modes: new Set(['palette', 'editor', 'xray']), hidden: [0, 2], revealed: [9, 31], @@ -60,14 +61,14 @@ export const CC2_TILESET_LAYOUT = { // FIXME this shouldn't be visible with seeing eye (or should it not spawn at all?) wall_invisible_revealed: [1, 2], wall_appearing: { - special: 'perception', + __special__: 'perception', modes: new Set(['palette', 'editor', 'xray']), hidden: [0, 2], revealed: [11, 31], }, wall: [1, 2], floor_letter: { - special: 'letter', + __special__: 'letter', base: [2, 2], letter_glyphs: { // Arrows @@ -96,7 +97,7 @@ export const CC2_TILESET_LAYOUT = { [9, 2], ], ice_block: { - special: 'perception', + __special__: 'perception', modes: new Set(['editor', 'xray']), hidden: [10, 2], revealed: [11, 2], @@ -109,7 +110,7 @@ export const CC2_TILESET_LAYOUT = { // LCD digit font green_chip: [9, 3], chip_extra: { - special: 'perception', + __special__: 'perception', modes: new Set(['palette', 'editor']), hidden: [11, 3], revealed: [10, 3], @@ -118,7 +119,7 @@ export const CC2_TILESET_LAYOUT = { bribe: [12, 3], speed_boots: [13, 3], canopy: { - special: 'perception', + __special__: 'perception', modes: new Set(['editor', 'xray']), hidden: [14, 3], revealed: [15, 3], @@ -126,6 +127,7 @@ export const CC2_TILESET_LAYOUT = { dynamite: [0, 4], dynamite_lit: { + __special__: 'visual-state', 0: [0, 4], 1: [1, 4], 2: [2, 4], @@ -133,12 +135,12 @@ export const CC2_TILESET_LAYOUT = { 4: [4, 4], }, bomb: { - special: 'bomb-fuse', + __special__: 'bomb-fuse', bomb: [5, 4], fuse: [7, 4], }, green_bomb: { - special: 'bomb-fuse', + __special__: 'bomb-fuse', bomb: [6, 4], fuse: [7, 4], }, @@ -158,16 +160,18 @@ export const CC2_TILESET_LAYOUT = { flame_jet_on: [[9, 5], [10, 5], [11, 5]], popdown_wall: [12, 5], popdown_floor: { - special: 'perception', + __special__: 'perception', modes: new Set(['palette', 'editor', 'xray']), - hidden: [12, 5], + hidden: { + __special__: 'visual-state', + depressed: [13, 5], + normal: [12, 5], + }, revealed: [13, 5], }, - // FIXME this should just be a visual_state really, but it would need to be able to nest with - // perception and that is not currently possible - popdown_floor_visible: [13, 5], no_sign: [14, 5], frame_block: { + __special__: 'arrows', base: [15, 5], arrows: [3, 10], }, @@ -181,10 +185,12 @@ export const CC2_TILESET_LAYOUT = { '#active-player-background': [6, 6], // TODO dopps can push but i don't think they have any other visuals doppelganger1: { + __special__: 'overlay', base: [7, 6], overlay: 'player', }, doppelganger2: { + __special__: 'overlay', base: [7, 6], overlay: 'player2', }, @@ -193,10 +199,12 @@ export const CC2_TILESET_LAYOUT = { button_red: [10, 6], button_brown: [11, 6], button_pink: { + __special__: 'wires', base: [0, 2], wired: [12, 6], }, button_black: { + __special__: 'wires', base: [0, 2], wired: [13, 6], }, @@ -227,14 +235,17 @@ export const CC2_TILESET_LAYOUT = { green_floor: [[0, 9], [1, 9], [2, 9], [3, 9]], purple_floor: [[4, 9], [5, 9], [6, 9], [7, 9]], green_wall: { + __special__: 'overlay', base: 'green_floor', overlay: [8, 9], }, purple_wall: { + __special__: 'overlay', base: 'purple_floor', overlay: [8, 9], }, trap: { + __special__: 'visual-state', closed: [9, 9], open: [10, 9], }, @@ -247,19 +258,19 @@ export const CC2_TILESET_LAYOUT = { fake_wall: [0, 10], fake_floor: { - special: 'perception', + __special__: 'perception', modes: new Set(['palette', 'editor', 'xray']), hidden: [0, 10], revealed: [10, 31], }, // Thin walls are built piecemeal from two tiles; the first is N/S, the second is E/W thin_walls: { - special: 'thin_walls', + __special__: 'thin_walls', thin_walls_ns: [1, 10], thin_walls_ew: [2, 10], }, - // TODO directional block arrows teleport_blue: { + __special__: 'wires', base: [0, 2], wired: [[4, 10], [5, 10], [6, 10], [7, 10]], }, @@ -277,6 +288,7 @@ export const CC2_TILESET_LAYOUT = { ], steel: { // Wiring! + __special__: 'wires', base: [15, 10], wired: [9, 26], wired_cross: [11, 26], @@ -310,12 +322,13 @@ export const CC2_TILESET_LAYOUT = { foil: [12, 12], turtle: { // Turtles draw atop fake water, but don't act like water otherwise + __special__: 'overlay', overlay: [13, 12], // TODO also 14 + 15, bobbing pseudorandomly base: 'water', }, walker: { - special: 'double-size-monster', + __special__: 'double-size-monster', base: [0, 13], vertical: [[1, 13], [2, 13], [3, 13], [4, 13], [5, 13], [6, 13], [7, 13]], horizontal: [[8, 13], [10, 13], [12, 13], [14, 13], [8, 14], [10, 14], [12, 14]], @@ -325,14 +338,14 @@ export const CC2_TILESET_LAYOUT = { stopwatch_bonus: [15, 14], blob: { - special: 'double-size-monster', + __special__: 'double-size-monster', base: [0, 15], vertical: [[1, 15], [2, 15], [3, 15], [4, 15], [5, 15], [6, 15], [7, 15]], horizontal: [[8, 15], [10, 15], [12, 15], [14, 15], [8, 16], [10, 16], [12, 16]], }, // (cc2 editor copy/paste outline) floor_mimic: { - special: 'perception', + __special__: 'perception', modes: new Set(['palette', 'editor', 'xray']), hidden: [0, 2], revealed: [14, 16], @@ -358,7 +371,7 @@ export const CC2_TILESET_LAYOUT = { }, rover: { - special: 'rover', + __special__: 'rover', direction: [10, 18], inert: [0, 18], teeth: [[0, 18], [8, 18]], @@ -389,43 +402,58 @@ export const CC2_TILESET_LAYOUT = { }, force_floor_n: { + __special__: 'scroll', base: [0, 19], - animate_height: 1, + scroll_region: [0, 1], }, force_floor_e: { + __special__: 'scroll', base: [3, 19], - animate_width: -1, + scroll_region: [-1, 0], }, force_floor_s: { + __special__: 'scroll', base: [1, 20], - animate_height: -1, + scroll_region: [0, -1], }, force_floor_w: { + __special__: 'scroll', base: [2, 20], - animate_width: 1, + scroll_region: [1, 0], }, teleport_green: [[4, 19], [5, 19], [6, 19], [7, 19]], teleport_yellow: [[8, 19], [9, 19], [10, 19], [11, 19]], transmogrifier: [[12, 19], [13, 19], [14, 19], [15, 19]], teleport_red: { + __special__: 'wires', base: [0, 2], wired: [[4, 20], [5, 20], [6, 20], [7, 20]], + /* FIXME don't animate when disabled, but that requires inspecting the level + wired: { + __special__: 'visual-state', + active: [[4, 20], [5, 20], [6, 20], [7, 20]], + inactive: [4, 20], + }, + */ }, slime: [[8, 20], [9, 20], [10, 20], [11, 20], [12, 20], [13, 20], [14, 20], [15, 20]], force_floor_all: [[0, 21], [1, 21], [2, 21], [3, 21], [4, 21], [5, 21], [6, 21], [7, 21]], // latches light_switch_off: { + __special__: 'wires', base: [14, 21], wired: [12, 21], }, light_switch_on: { + __special__: 'wires', base: [14, 21], wired: [13, 21], }, thief_keys: [15, 21], player: { + __special__: 'visual-state', normal: { north: [0, 22], south: [0, 23], @@ -471,6 +499,7 @@ export const CC2_TILESET_LAYOUT = { // Do a quick spin I guess?? player1_exit: [[0, 22], [8, 22], [0, 23], [8, 23]], bogus_player_win: { + __special__: 'overlay', overlay: [0, 23], base: 'exit', }, @@ -481,14 +510,17 @@ export const CC2_TILESET_LAYOUT = { west: [[6, 24], [7, 24]], }, bogus_player_drowned: { + __special__: 'overlay', overlay: [5, 5], // splash base: 'water', }, bogus_player_burned_fire: { + __special__: 'overlay', overlay: [2, 5], // explosion frame 3 base: 'fire', }, bogus_player_burned: { + __special__: 'overlay', overlay: [2, 5], // explosion frame 3 base: 'floor', }, @@ -500,7 +532,7 @@ export const CC2_TILESET_LAYOUT = { ], logic_gate: { - special: 'logic-gate', + __special__: 'logic-gate', logic_gate_tiles: { 'latch-ccw': { north: [8, 21], @@ -552,6 +584,7 @@ export const CC2_TILESET_LAYOUT = { '#powered': [15, 26], player2: { + __special__: 'visual-state', normal: { north: [0, 27], south: [0, 28], @@ -603,7 +636,7 @@ export const CC2_TILESET_LAYOUT = { ], railroad: { - special: 'railroad', + __special__: 'railroad', base: [9, 10], railroad_ties: { ne: [0, 30], @@ -656,7 +689,7 @@ export const TILE_WORLD_TILESET_LAYOUT = { wall_invisible_revealed: [0, 1], // FIXME in cc1 tilesets these are opaque so they should draw at the terrain layer thin_walls: { - special: 'thin_walls_cc1', + __special__: 'thin_walls_cc1', north: [0, 6], west: [0, 7], south: [0, 8], @@ -702,6 +735,7 @@ export const TILE_WORLD_TILESET_LAYOUT = { teleport_blue: [2, 9], bomb: [2, 10], trap: { + __special__: 'visual-state', closed: [2, 11], open: [2, 11], }, @@ -793,6 +827,7 @@ export const TILE_WORLD_TILESET_LAYOUT = { cleats: [6, 10], suction_boots: [6, 11], player: { + __special__: 'visual-state', normal: { north: [6, 12], south: [6, 14], @@ -837,6 +872,7 @@ export const LL_TILESET_LAYOUT = Object.assign({}, CC2_TILESET_LAYOUT, { // Extra player sprites player: Object.assign({}, CC2_TILESET_LAYOUT.player, { + __special__: 'visual-state', pushing: { north: [[8, 24], [0, 34], [8, 24], [1, 34]], east: [[9, 24], [2, 34], [9, 24], [3, 34]], @@ -860,6 +896,7 @@ export const LL_TILESET_LAYOUT = Object.assign({}, CC2_TILESET_LAYOUT, { slimed: [1, 38], }), player2: Object.assign({}, CC2_TILESET_LAYOUT.player2, { + __special__: 'visual-state', pushing: { north: [[8, 29], [8, 34], [8, 29], [9, 34]], east: [[9, 29], [10, 34], [9, 29], [11, 34]], @@ -883,10 +920,12 @@ export const LL_TILESET_LAYOUT = Object.assign({}, CC2_TILESET_LAYOUT, { slimed: [1, 38], }), bogus_player_burned_fire: { + __special__: 'overlay', overlay: [6, 33], base: 'fire', }, bogus_player_burned: { + __special__: 'overlay', overlay: [6, 33], base: 'floor', }, @@ -895,6 +934,7 @@ export const LL_TILESET_LAYOUT = Object.assign({}, CC2_TILESET_LAYOUT, { popwall2: [9, 32], gift_bow: [10, 32], circuit_block: { + __special__: 'wires', base: [13, 32], wired: [11, 32], wired_cross: [12, 32], @@ -914,12 +954,62 @@ export const LL_TILESET_LAYOUT = Object.assign({}, CC2_TILESET_LAYOUT, { south: [[2, 37], [1, 37], [0, 37], [3, 37]], west: [[6, 37], [5, 37], [4, 37], [7, 37]], }, + // Pressed buttons + button_blue: { + __special__: 'visual-state', + released: [8, 6], + pressed: [8, 37], + }, + button_green: { + __special__: 'visual-state', + released: [9, 6], + pressed: [9, 37], + }, + button_red: { + __special__: 'visual-state', + released: [10, 6], + pressed: [10, 37], + }, + button_brown: { + __special__: 'visual-state', + released: [11, 6], + pressed: [11, 37], + }, + button_pink: { + __special__: 'wires', + base: [0, 2], + wired: { + __special__: 'visual-state', + released: [12, 6], + pressed: [12, 37], + }, + }, + button_black: { + __special__: 'wires', + __special__: 'wires', + base: [0, 2], + wired: { + __special__: 'visual-state', + released: [13, 6], + pressed: [13, 37], + }, + }, + button_orange: { + __special__: 'visual-state', + released: [14, 6], + pressed: [14, 37], + }, + button_gray: { + __special__: 'visual-state', + released: [11, 9], + pressed: [15, 37], + }, // Custom VFX splash_slime: [[0, 38], [1, 38], [2, 38], [3, 38]], teleport_flash: [[4, 38], [5, 38], [6, 38], [7, 38]], chip_extra: { - special: 'perception', + __special__: 'perception', modes: new Set(['palette', 'editor']), hidden: [[11, 3], [0, 39], [1, 39], [0, 39]], revealed: [10, 3], @@ -987,11 +1077,29 @@ export class Tileset { this.draw_type(tile.type.name, tile, packet); } + // Draws a tile type, given by name. Passing in a tile is optional, but + // without it you'll get defaults. + draw_type(name, tile, packet) { + let drawspec = this.layout[name]; + if (drawspec === null) { + // This is explicitly never drawn (used for extra visual-only frills that don't exist in + // some tilesets) + return; + } + if (! drawspec) { + // This is just missing + console.error(`Don't know how to draw tile type ${name}!`); + return; + } + + this.draw_drawspec(drawspec, name, tile, packet); + } + // Draw a "standard" drawspec, which is either: // - a single tile: [x, y] // - an animation: [[x0, y0], [x1, y1], ...] // - a directional tile: { north: T, east: T, ... } where T is either of the above - _draw_standard(drawspec, tile, packet) { + _draw_standard(drawspec, name, tile, packet) { // If we have an object, it must be a table of directions let coords = drawspec; if (!(coords instanceof Array)) { @@ -1041,190 +1149,92 @@ export class Tileset { packet.blit(coords[0], coords[1]); } - _draw_letter(drawspec, tile, packet) { - this._draw_standard(drawspec.base, tile, packet); - - let glyph = tile ? tile.overlaid_glyph : "?"; - if (drawspec.letter_glyphs[glyph]) { - let [x, y] = drawspec.letter_glyphs[glyph]; - // XXX size is hardcoded here, but not below, meh - packet.blit(x, y, 0, 0, 0.5, 0.5, 0.25, 0.25); + // Simple overlaying used for green/purple toggle tiles and doppelgangers. Draw the base (a + // type name or drawspec), then draw the overlay (either a type name or a regular draw spec). + _draw_overlay(drawspec, name, tile, packet) { + // TODO chance of infinite recursion here + if (typeof drawspec.base === 'string') { + this.draw_type(drawspec.base, tile, packet); } else { - // Look for a range - let u = glyph.charCodeAt(0); - for (let rangedef of drawspec.letter_ranges) { - if (rangedef.range[0] <= u && u < rangedef.range[1]) { - let t = u - rangedef.range[0]; - let x = rangedef.x0 + rangedef.w * (t % rangedef.columns); - let y = rangedef.y0 + rangedef.h * Math.floor(t / rangedef.columns); - packet.blit(x, y, 0, 0, rangedef.w, rangedef.h, - (1 - rangedef.w) / 2, (1 - rangedef.h) / 2); - break; - } - } + this.draw_drawspec(drawspec.base, name, tile, packet); } - } - - _draw_thin_walls(drawspec, tile, packet) { - let edges = tile ? tile.edges : 0x0f; - - // TODO it would be /extremely/ cool to join corners diagonally, but i can't do that without - // access to the context, which defeats the whole purpose of this scheme. damn - if (edges & DIRECTIONS['north'].bit) { - packet.blit(...drawspec.thin_walls_ns, 0, 0, 1, 0.5); - } - if (edges & DIRECTIONS['east'].bit) { - packet.blit(...drawspec.thin_walls_ew, 0.5, 0, 0.5, 1); - } - if (edges & DIRECTIONS['south'].bit) { - packet.blit(...drawspec.thin_walls_ns, 0, 0.5, 1, 0.5); - } - if (edges & DIRECTIONS['west'].bit) { - packet.blit(...drawspec.thin_walls_ew, 0, 0, 0.5, 1); - } - } - - _draw_thin_walls_cc1(drawspec, tile, packet) { - let edges = tile ? tile.edges : 0x0f; - - // This is kinda best-effort since the tiles are opaque and not designed to combine - if (edges === (DIRECTIONS['south'].bit | DIRECTIONS['east'].bit)) { - packet.blit(...drawspec.southeast); - } - else if (edges & DIRECTIONS['north'].bit) { - packet.blit(...drawspec.north); - } - else if (edges & DIRECTIONS['east'].bit) { - packet.blit(...drawspec.east); - } - else if (edges & DIRECTIONS['south'].bit) { - packet.blit(...drawspec.south); + if (typeof drawspec.overlay === 'string') { + this.draw_type(drawspec.overlay, tile, packet); } else { - packet.blit(...drawspec.west); + this.draw_drawspec(drawspec.overlay, name, tile, packet); } } - _draw_bomb_fuse(drawspec, tile, packet) { - // Draw the base bomb - this._draw_standard(drawspec.bomb, tile, packet); - - // The fuse is made up of four quarter-tiles and animates... um... at a rate. I cannot - // tell. I have spent over an hour poring over this and cannot find a consistent pattern. - // It might be random! I'm gonna say it loops every 0.3 seconds = 18 frames, so 4.5 frames - // per cel, I guess. No one will know. (But... I'll know.) - // Also it's drawn in the upper right, that's important. - let cel = Math.floor(packet.tic / 0.3 * 4) % 4; - packet.blit(...drawspec.fuse, 0.5 * (cel % 2), 0.5 * Math.floor(cel / 2), 0.5, 0.5, 0.5, 0); + // Scrolling region, used for force floors + _draw_scroll(drawspec, name, tile, packet) { + let [x, y] = drawspec.base; + let duration = 3 * this.animation_slowdown; + x += drawspec.scroll_region[0] * (packet.tic % duration / duration); + y += drawspec.scroll_region[1] * (packet.tic % duration / duration); + // Round to pixels + x = Math.floor(x * this.size_x + 0.5) / this.size_x; + y = Math.floor(y * this.size_y + 0.5) / this.size_y; + packet.blit(x, y); } - _draw_double_size_monster(drawspec, tile, packet) { - // CC2's tileset has double-size art for blobs and walkers that spans the tile they're - // moving from AND the tile they're moving into. - // First, of course, this only happens if they're moving at all. - if (! tile || ! tile.movement_speed) { - this._draw_standard(drawspec.base, tile, packet); - return; - } + _draw_wires(drawspec, name, tile, packet) { + // This /should/ match CC2's draw order exactly, based on experimentation + let wire_radius = this.layout['#wire-width'] / 2; + // TODO circuit block with a lightning bolt is always powered + // TODO circuit block in motion doesn't inherit cell's power + if (tile && tile.wire_directions) { + // Draw the base tile + packet.blit(drawspec.base[0], drawspec.base[1]); - // They only support horizontal and vertical moves, not all four directions. The other two - // directions are simply the animations played in reverse. - let axis_cels; - let w = 1, h = 1, x = 0, y = 0, reverse = false; - if (tile.direction === 'north') { - axis_cels = drawspec.vertical; - reverse = true; - h = 2; - } - else if (tile.direction === 'south') { - axis_cels = drawspec.vertical; - h = 2; - y = -1; - } - else if (tile.direction === 'west') { - axis_cels = drawspec.horizontal; - reverse = true; - w = 2; - } - else if (tile.direction === 'east') { - axis_cels = drawspec.horizontal; - w = 2; - x = -1; - } + this._draw_fourway_tile_power(tile, tile.wire_directions, packet); - let p = tile.movement_progress(packet.tic % 1, packet.update_rate); - p = Math.min(p, 0.999); // FIXME hack for differing movement counters - let index = Math.floor(p * (axis_cels.length + 1)); - if (index === 0 || index > axis_cels.length) { - this._draw_standard(drawspec.base, tile, packet); - } - else { - let cel = reverse ? axis_cels[axis_cels.length - index] : axis_cels[index - 1]; - packet.blit_aligned(...cel, 0, 0, w, h, x, y); - } - } - - _draw_rover(drawspec, tile, packet) { - // Rovers draw fairly normally (with their visual_state giving the monster they're copying), - // but they also have an overlay indicating their direction - let state = tile ? tile.type.visual_state(tile) : 'inert'; - this._draw_standard(drawspec[state], tile, packet); - - if (! tile) - return; - - // The direction overlay is one of four quarter-tiles, drawn about in the center of the - // rover but shifted an eighth of a tile in the direction in question - let overlay_position = this._rotate(tile.direction, 0.25, 0.125, 0.75, 0.625); - let index = {north: 0, east: 1, west: 2, south: 3}[tile.direction]; - if (index === undefined) - return; - packet.blit( - ...drawspec.direction, - 0.5 * (index % 2), 0.5 * Math.floor(index / 2), 0.5, 0.5, - overlay_position[0], overlay_position[1]); - } - - _draw_logic_gate(drawspec, tile, packet) { - // Layer 1: wiring state - // Always draw the unpowered wire base - let unpowered_coords = this.layout['#unpowered']; - let powered_coords = this.layout['#powered']; - packet.blit(...unpowered_coords); - if (tile && tile.cell) { - // What goes on top varies a bit... - let r = this.layout['#wire-width'] / 2; - if (tile.gate_type === 'not' || tile.gate_type === 'counter') { - this._draw_fourway_tile_power(tile, 0x0f, packet); + // Then draw the wired tile on top of it all + if (tile.wire_directions === 0x0f && drawspec.wired_cross) { + // FIXME oopsydaisy, order matters for the cross part + this.draw_drawspec(drawspec.wired_cross, name, tile, packet); } else { - if (tile.powered_edges & DIRECTIONS[tile.direction].bit) { - // Output (on top) - let [x0, y0, x1, y1] = this._rotate(tile.direction, 0.5 - r, 0, 0.5 + r, 0.5); - packet.blit(powered_coords[0], powered_coords[1], x0, y0, x1 - x0, y1 - y0); - } - if (tile.powered_edges & DIRECTIONS[DIRECTIONS[tile.direction].right].bit) { - // Right input, which includes the middle - // This actually covers the entire lower right corner, for bent inputs. - let [x0, y0, x1, y1] = this._rotate(tile.direction, 0.5 - r, 0.5 - r, 1, 1); - packet.blit(powered_coords[0], powered_coords[1], x0, y0, x1 - x0, y1 - y0); - } - if (tile.powered_edges & DIRECTIONS[DIRECTIONS[tile.direction].left].bit) { - // Left input, which does not include the middle - // This actually covers the entire lower left corner, for bent inputs. - let [x0, y0, x1, y1] = this._rotate(tile.direction, 0, 0.5 - r, 0.5 - r, 1); - packet.blit(powered_coords[0], powered_coords[1], x0, y0, x1 - x0, y1 - y0); - } + this.draw_drawspec(drawspec.wired, name, tile, packet); + } + } + else { + // There's no wiring here, so just draw the base and then draw the wired part on top + // as normal. If the wired part is optional (as is the case for flooring in the CC2 + // tileset), draw the base as normal instead. + if (drawspec.is_wired_optional) { + this.draw_drawspec(drawspec.base, name, tile, packet); + } + else { + packet.blit(drawspec.base[0], drawspec.base[1]); + this.draw_drawspec(drawspec.wired, name, tile, packet); } } - // Layer 2: the tile itself - this._draw_standard(drawspec.logic_gate_tiles[tile.gate_type], tile, packet); - // Layer 3: counter number - if (tile.gate_type === 'counter') { - packet.blit(0, 3, tile.memory * 0.75, 0, 0.75, 1, 0.125, 0); + // Wired tiles may also have tunnels, drawn on top of everything else + if (tile && tile.wire_tunnel_directions) { + let tunnel_coords = this.layout['#wire-tunnel']; + let tunnel_width = 6/32; + let tunnel_length = 12/32; + let tunnel_offset = (1 - tunnel_width) / 2; + if (tile.wire_tunnel_directions & DIRECTIONS['north'].bit) { + packet.blit(tunnel_coords[0], tunnel_coords[1], + tunnel_offset, 0, tunnel_width, tunnel_length); + } + if (tile.wire_tunnel_directions & DIRECTIONS['south'].bit) { + packet.blit(tunnel_coords[0], tunnel_coords[1], + tunnel_offset, 1 - tunnel_length, tunnel_width, tunnel_length); + } + if (tile.wire_tunnel_directions & DIRECTIONS['west'].bit) { + packet.blit(tunnel_coords[0], tunnel_coords[1], + 0, tunnel_offset, tunnel_length, tunnel_width); + } + if (tile.wire_tunnel_directions & DIRECTIONS['east'].bit) { + packet.blit(tunnel_coords[0], tunnel_coords[1], + 1 - tunnel_length, tunnel_offset, tunnel_length, tunnel_width); + } } } @@ -1264,225 +1274,90 @@ export class Tileset { packet.blit(drawspec[0], drawspec[1], x0, y0, x1 - x0, y1 - y0); } - _draw_railroad(drawspec, tile, packet) { - // All railroads have regular gravel underneath - // TODO would be nice to disambiguate since it's possible to have nothing visible - this._draw_standard(this.layout['gravel'], tile, packet); - // FIXME what do i draw if there's no tile? - let part_order = ['ne', 'se', 'sw', 'nw', 'ew', 'ns']; - let visible_parts = []; - let topmost_part = null; - for (let [i, part] of part_order.entries()) { - if (tile && (tile.tracks & (1 << i))) { - if (tile.track_switch === i) { - topmost_part = part; + _draw_letter(drawspec, name, tile, packet) { + this.draw_drawspec(drawspec.base, name, tile, packet); + + let glyph = tile ? tile.overlaid_glyph : "?"; + if (drawspec.letter_glyphs[glyph]) { + let [x, y] = drawspec.letter_glyphs[glyph]; + // XXX size is hardcoded here, but not below, meh + packet.blit(x, y, 0, 0, 0.5, 0.5, 0.25, 0.25); + } + else { + // Look for a range + let u = glyph.charCodeAt(0); + for (let rangedef of drawspec.letter_ranges) { + if (rangedef.range[0] <= u && u < rangedef.range[1]) { + let t = u - rangedef.range[0]; + let x = rangedef.x0 + rangedef.w * (t % rangedef.columns); + let y = rangedef.y0 + rangedef.h * Math.floor(t / rangedef.columns); + packet.blit(x, y, 0, 0, rangedef.w, rangedef.h, + (1 - rangedef.w) / 2, (1 - rangedef.h) / 2); + break; } - visible_parts.push(part); } } - - let has_switch = (tile && tile.track_switch !== null); - for (let part of visible_parts) { - this._draw_standard(drawspec.railroad_ties[part], tile, packet); - } - let tracks = has_switch ? drawspec.railroad_inactive : drawspec.railroad_active; - for (let part of visible_parts) { - if (part !== topmost_part) { - this._draw_standard(tracks[part], tile, packet); - } - } - - if (topmost_part) { - this._draw_standard(drawspec.railroad_active[topmost_part], tile, packet); - } - if (has_switch) { - this._draw_standard(drawspec.railroad_switch, tile, packet); - } } - // Draws a tile type, given by name. Passing in a tile is optional, but - // without it you'll get defaults. - draw_type(name, tile, packet) { - let drawspec = this.layout[name]; - if (drawspec === null) { - // This is explicitly never drawn (used for extra visual-only frills that don't exist in - // some tilesets) - return; - } - if (! drawspec) { - // This is just missing - console.error(`Don't know how to draw tile type ${name}!`); - return; - } + _draw_thin_walls(drawspec, name, tile, packet) { + let edges = tile ? tile.edges : 0x0f; - if (drawspec.overlay) { - // Goofy overlay thing used for green/purple toggle tiles and - // southeast thin walls. Draw the base (a type name or drawspec), then draw - // the overlay (either a type name or a regular draw spec). - // TODO chance of infinite recursion here - if (typeof drawspec.base === 'string') { - this.draw_type(drawspec.base, tile, packet); - } - else { - this._draw_standard(drawspec.base, tile, packet); - } - if (typeof drawspec.overlay === 'string') { - this.draw_type(drawspec.overlay, tile, packet); - return; - } - else { - drawspec = drawspec.overlay; - } + // TODO it would be /extremely/ cool to join corners diagonally, but i can't do that without + // access to the context, which defeats the whole purpose of this scheme. damn + if (edges & DIRECTIONS['north'].bit) { + packet.blit(...drawspec.thin_walls_ns, 0, 0, 1, 0.5); } - - // TODO shift everything to use this style, this is ridiculous - if (drawspec.special) { - if (drawspec.special === 'letter') { - this._draw_letter(drawspec, tile, packet); - return; - } - else if (drawspec.special === 'thin_walls') { - this._draw_thin_walls(drawspec, tile, packet); - return; - } - else if (drawspec.special === 'thin_walls_cc1') { - this._draw_thin_walls_cc1(drawspec, tile, packet); - return; - } - else if (drawspec.special === 'bomb-fuse') { - this._draw_bomb_fuse(drawspec, tile, packet); - return; - } - else if (drawspec.special === 'double-size-monster') { - this._draw_double_size_monster(drawspec, tile, packet); - return; - } - else if (drawspec.special === 'rover') { - this._draw_rover(drawspec, tile, packet); - return; - } - else if (drawspec.special === 'perception') { - if (drawspec.modes.has(packet.perception)) { - drawspec = drawspec.revealed; - } - else { - drawspec = drawspec.hidden; - } - } - else if (drawspec.special === 'logic-gate') { - this._draw_logic_gate(drawspec, tile, packet); - return; - } - else if (drawspec.special === 'railroad') { - this._draw_railroad(drawspec, tile, packet); - return; - } + if (edges & DIRECTIONS['east'].bit) { + packet.blit(...drawspec.thin_walls_ew, 0.5, 0, 0.5, 1); } - - let coords = drawspec; - if (drawspec.wired) { - // This /should/ match CC2's draw order exactly, based on experimentation - let wire_radius = this.layout['#wire-width'] / 2; - // TODO circuit block with a lightning bolt is always powered - // TODO circuit block in motion doesn't inherit cell's power - if (tile && tile.wire_directions) { - // Draw the base tile - packet.blit(drawspec.base[0], drawspec.base[1]); - - this._draw_fourway_tile_power(tile, tile.wire_directions, packet); - - // Then draw the wired tile on top of it all - if (tile.wire_directions === 0x0f && drawspec.wired_cross) { - // FIXME oopsydaisy, order matters for the cross part - coords = drawspec.wired_cross; - } - else { - coords = drawspec.wired; - } - } - else { - // There's no wiring here, so just draw the base and then draw the wired part on top - // as normal. If the wired part is optional (as is the case for flooring in the CC2 - // tileset), draw the base as normal instead. - if (drawspec.is_wired_optional) { - coords = drawspec.base; - } - else { - packet.blit(drawspec.base[0], drawspec.base[1]); - coords = drawspec.wired; - } - } + if (edges & DIRECTIONS['south'].bit) { + packet.blit(...drawspec.thin_walls_ns, 0, 0.5, 1, 0.5); } - else if (drawspec.arrows) { - // Directional blocks have a specific overlay, but draw the base first - coords = drawspec.base; + if (edges & DIRECTIONS['west'].bit) { + packet.blit(...drawspec.thin_walls_ew, 0, 0, 0.5, 1); } - else if (drawspec.animate_width) { - // Force floors animate their... cutout, I guess? - let [x, y] = drawspec.base; - let duration = 3 * this.animation_slowdown; - x += drawspec.animate_width * (packet.tic % duration / duration); - // Round to tile width - x = Math.floor(x * this.size_x + 0.5) / this.size_x; - coords = [x, y]; + } + + _draw_thin_walls_cc1(drawspec, name, tile, packet) { + let edges = tile ? tile.edges : 0x0f; + + // This is kinda best-effort since the tiles are opaque and not designed to combine + if (edges === (DIRECTIONS['south'].bit | DIRECTIONS['east'].bit)) { + packet.blit(...drawspec.southeast); } - else if (drawspec.animate_height) { - // Same, but along the other axis - let [x, y] = drawspec.base; - let duration = 3 * this.animation_slowdown; - y += drawspec.animate_height * (packet.tic % duration / duration); - // Round to tile height - y = Math.floor(y * this.size_y + 0.5) / this.size_y; - coords = [x, y]; + else if (edges & DIRECTIONS['north'].bit) { + packet.blit(...drawspec.north); } - - // Apply custom per-type visual states - if (TILE_TYPES[name] && TILE_TYPES[name].visual_state) { - // Note that these accept null, too, and return a default - let state = TILE_TYPES[name].visual_state(tile); - // If it's a string, that's an alias for another state - if (typeof coords[state] === 'string') { - coords = coords[coords[state]]; - } - else { - coords = coords[state]; - } - - if (! coords) { - console.warn("No such state", state, "for tile", name, tile); - } + else if (edges & DIRECTIONS['east'].bit) { + packet.blit(...drawspec.east); } - - this._draw_standard(coords, tile, packet); - - // Wired tiles may also have tunnels, drawn on top of everything else - if (drawspec.wired && tile && tile.wire_tunnel_directions) { - let tunnel_coords = this.layout['#wire-tunnel']; - let tunnel_width = 6/32; - let tunnel_length = 12/32; - let tunnel_offset = (1 - tunnel_width) / 2; - if (tile.wire_tunnel_directions & DIRECTIONS['north'].bit) { - packet.blit(tunnel_coords[0], tunnel_coords[1], - tunnel_offset, 0, tunnel_width, tunnel_length); - } - if (tile.wire_tunnel_directions & DIRECTIONS['south'].bit) { - packet.blit(tunnel_coords[0], tunnel_coords[1], - tunnel_offset, 1 - tunnel_length, tunnel_width, tunnel_length); - } - if (tile.wire_tunnel_directions & DIRECTIONS['west'].bit) { - packet.blit(tunnel_coords[0], tunnel_coords[1], - 0, tunnel_offset, tunnel_length, tunnel_width); - } - if (tile.wire_tunnel_directions & DIRECTIONS['east'].bit) { - packet.blit(tunnel_coords[0], tunnel_coords[1], - 1 - tunnel_length, tunnel_offset, tunnel_length, tunnel_width); - } + else if (edges & DIRECTIONS['south'].bit) { + packet.blit(...drawspec.south); } + else { + packet.blit(...drawspec.west); + } + } - // Directional blocks have arrows drawn on top - // TODO does cc2 draw even if there are no arrows? - if (drawspec.arrows && tile && tile.arrows) { + _draw_bomb_fuse(drawspec, name, tile, packet) { + // Draw the base bomb + this.draw_drawspec(drawspec.bomb, name, tile, packet); + + // The fuse is made up of four quarter-tiles and animates... um... at a rate. I cannot + // tell. I have spent over an hour poring over this and cannot find a consistent pattern. + // It might be random! I'm gonna say it loops every 0.3 seconds = 18 frames, so 4.5 frames + // per cel, I guess. No one will know. (But... I'll know.) + // Also it's drawn in the upper right, that's important. + let cel = Math.floor(packet.tic / 0.3 * 4) % 4; + packet.blit(...drawspec.fuse, 0.5 * (cel % 2), 0.5 * Math.floor(cel / 2), 0.5, 0.5, 0.5, 0); + } + + // Frame blocks have an arrow overlay + _draw_arrows(drawspec, name, tile, packet) { + this.draw_drawspec(drawspec.base, name, tile, packet); + + if (tile && tile.arrows) { let [x, y] = drawspec.arrows; let x0 = 0.25, y0 = 0.25, x1 = 0.75, y1 = 0.75; if (tile.arrows.has('north')) { @@ -1501,6 +1376,225 @@ export class Tileset { } } + _draw_visual_state(drawspec, name, tile, packet) { + // Apply custom per-type visual states + // Note that these accept null, too, and return a default + let state = TILE_TYPES[name].visual_state(tile); + // If it's a string, that's an alias for another state + if (typeof drawspec[state] === 'string') { + state = drawspec[state]; + } + if (! drawspec[state]) { + console.warn("No such state", state, "for tile", name, tile); + } + + this.draw_drawspec(drawspec[state], name, tile, packet); + } + + _draw_double_size_monster(drawspec, name, tile, packet) { + // CC2's tileset has double-size art for blobs and walkers that spans the tile they're + // moving from AND the tile they're moving into. + // First, of course, this only happens if they're moving at all. + if (! tile || ! tile.movement_speed) { + this.draw_drawspec(drawspec.base, name, tile, packet); + return; + } + + // They only support horizontal and vertical moves, not all four directions. The other two + // directions are simply the animations played in reverse. + let axis_cels; + let w = 1, h = 1, x = 0, y = 0, reverse = false; + if (tile.direction === 'north') { + axis_cels = drawspec.vertical; + reverse = true; + h = 2; + } + else if (tile.direction === 'south') { + axis_cels = drawspec.vertical; + h = 2; + y = -1; + } + else if (tile.direction === 'west') { + axis_cels = drawspec.horizontal; + reverse = true; + w = 2; + } + else if (tile.direction === 'east') { + axis_cels = drawspec.horizontal; + w = 2; + x = -1; + } + + let p = tile.movement_progress(packet.tic % 1, packet.update_rate); + p = Math.min(p, 0.999); // FIXME hack for differing movement counters + let index = Math.floor(p * (axis_cels.length + 1)); + if (index === 0 || index > axis_cels.length) { + this.draw_drawspec(drawspec.base, name, tile, packet); + } + else { + let cel = reverse ? axis_cels[axis_cels.length - index] : axis_cels[index - 1]; + packet.blit_aligned(...cel, 0, 0, w, h, x, y); + } + } + + _draw_rover(drawspec, name, tile, packet) { + // Rovers draw fairly normally (with their visual_state giving the monster they're copying), + // but they also have an overlay indicating their direction + let state = tile ? tile.type.visual_state(tile) : 'inert'; + this.draw_drawspec(drawspec[state], name, tile, packet); + + if (! tile) + return; + + // The direction overlay is one of four quarter-tiles, drawn about in the center of the + // rover but shifted an eighth of a tile in the direction in question + let overlay_position = this._rotate(tile.direction, 0.25, 0.125, 0.75, 0.625); + let index = {north: 0, east: 1, west: 2, south: 3}[tile.direction]; + if (index === undefined) + return; + packet.blit( + ...drawspec.direction, + 0.5 * (index % 2), 0.5 * Math.floor(index / 2), 0.5, 0.5, + overlay_position[0], overlay_position[1]); + } + + _draw_logic_gate(drawspec, name, tile, packet) { + // Layer 1: wiring state + // Always draw the unpowered wire base + let unpowered_coords = this.layout['#unpowered']; + let powered_coords = this.layout['#powered']; + packet.blit(...unpowered_coords); + if (tile && tile.cell) { + // What goes on top varies a bit... + let r = this.layout['#wire-width'] / 2; + if (tile.gate_type === 'not' || tile.gate_type === 'counter') { + this._draw_fourway_tile_power(tile, 0x0f, packet); + } + else { + if (tile.powered_edges & DIRECTIONS[tile.direction].bit) { + // Output (on top) + let [x0, y0, x1, y1] = this._rotate(tile.direction, 0.5 - r, 0, 0.5 + r, 0.5); + packet.blit(powered_coords[0], powered_coords[1], x0, y0, x1 - x0, y1 - y0); + } + if (tile.powered_edges & DIRECTIONS[DIRECTIONS[tile.direction].right].bit) { + // Right input, which includes the middle + // This actually covers the entire lower right corner, for bent inputs. + let [x0, y0, x1, y1] = this._rotate(tile.direction, 0.5 - r, 0.5 - r, 1, 1); + packet.blit(powered_coords[0], powered_coords[1], x0, y0, x1 - x0, y1 - y0); + } + if (tile.powered_edges & DIRECTIONS[DIRECTIONS[tile.direction].left].bit) { + // Left input, which does not include the middle + // This actually covers the entire lower left corner, for bent inputs. + let [x0, y0, x1, y1] = this._rotate(tile.direction, 0, 0.5 - r, 0.5 - r, 1); + packet.blit(powered_coords[0], powered_coords[1], x0, y0, x1 - x0, y1 - y0); + } + } + } + + // Layer 2: the tile itself + this.draw_drawspec(drawspec.logic_gate_tiles[tile.gate_type], name, tile, packet); + + // Layer 3: counter number + if (tile.gate_type === 'counter') { + packet.blit(0, 3, tile.memory * 0.75, 0, 0.75, 1, 0.125, 0); + } + } + + _draw_railroad(drawspec, name, tile, packet) { + // All railroads have regular gravel underneath + // TODO would be nice to disambiguate since it's possible to have nothing visible + this.draw_drawspec(this.layout['gravel'], name, tile, packet); + + // FIXME what do i draw if there's no tile? + let part_order = ['ne', 'se', 'sw', 'nw', 'ew', 'ns']; + let visible_parts = []; + let topmost_part = null; + for (let [i, part] of part_order.entries()) { + if (tile && (tile.tracks & (1 << i))) { + if (tile.track_switch === i) { + topmost_part = part; + } + visible_parts.push(part); + } + } + + let has_switch = (tile && tile.track_switch !== null); + for (let part of visible_parts) { + this.draw_drawspec(drawspec.railroad_ties[part], name, tile, packet); + } + let tracks = has_switch ? drawspec.railroad_inactive : drawspec.railroad_active; + for (let part of visible_parts) { + if (part !== topmost_part) { + this.draw_drawspec(tracks[part], name, tile, packet); + } + } + + if (topmost_part) { + this.draw_drawspec(drawspec.railroad_active[topmost_part], name, tile, packet); + } + if (has_switch) { + this.draw_drawspec(drawspec.railroad_switch, name, tile, packet); + } + } + + draw_drawspec(drawspec, name, tile, packet) { + if (drawspec.__special__) { + if (drawspec.__special__ === 'overlay') { + this._draw_overlay(drawspec, name, tile, packet); + } + else if (drawspec.__special__ === 'scroll') { + this._draw_scroll(drawspec, name, tile, packet); + } + else if (drawspec.__special__ === 'wires') { + this._draw_wires(drawspec, name, tile, packet); + } + else if (drawspec.__special__ === 'letter') { + this._draw_letter(drawspec, name, tile, packet); + } + else if (drawspec.__special__ === 'thin_walls') { + this._draw_thin_walls(drawspec, name, tile, packet); + } + else if (drawspec.__special__ === 'thin_walls_cc1') { + this._draw_thin_walls_cc1(drawspec, name, tile, packet); + } + else if (drawspec.__special__ === 'bomb-fuse') { + this._draw_bomb_fuse(drawspec, name, tile, packet); + } + else if (drawspec.__special__ === 'arrows') { + this._draw_arrows(drawspec, name, tile, packet); + } + else if (drawspec.__special__ === 'visual-state') { + this._draw_visual_state(drawspec, name, tile, packet); + } + else if (drawspec.__special__ === 'double-size-monster') { + this._draw_double_size_monster(drawspec, name, tile, packet); + } + else if (drawspec.__special__ === 'rover') { + this._draw_rover(drawspec, name, tile, packet); + } + else if (drawspec.__special__ === 'perception') { + if (drawspec.modes.has(packet.perception)) { + this.draw_drawspec(drawspec.revealed, name, tile, packet); + } + else { + this.draw_drawspec(drawspec.hidden, name, tile, packet); + } + } + else if (drawspec.__special__ === 'logic-gate') { + this._draw_logic_gate(drawspec, name, tile, packet); + } + else if (drawspec.__special__ === 'railroad') { + this._draw_railroad(drawspec, name, tile, packet); + } + else { + console.error(`No such special ${drawspec.__special__} for ${name}`); + } + return; + } + + this._draw_standard(drawspec, name, tile, packet); + } + _rotate(direction, x0, y0, x1, y1) { if (direction === 'east') { return [1 - y1, x0, 1 - y0, x1]; diff --git a/js/tiletypes.js b/js/tiletypes.js index 28f7377..4d11ed2 100644 --- a/js/tiletypes.js +++ b/js/tiletypes.js @@ -141,6 +141,16 @@ function player_visual_state(me) { } } +function button_visual_state(me) { + if (me && me.cell) { + let actor = me.cell.get_actor(); + if (actor && ! actor.movement_cooldown) { + return 'pressed'; + } + } + return 'released'; +}; + // Logic for chasing after the player (or running away); shared by both teeth and mimics function pursue_player(me, level) { // Teeth can only move the first 4 of every 8 tics, and mimics only the first 4 of every 16, @@ -336,25 +346,11 @@ const TILE_TYPES = { popdown_floor: { layer: LAYERS.terrain, blocks_collision: COLLISION.block_cc1 | COLLISION.block_cc2, - // FIXME get rid of popdown_floor_visible and use visual_state for this, but requires some - // changes to the tileset stuff - on_ready(me, level) { - // Start out as a visible floor if there's an actor or item on us - if (me.cell.get_item() || me.cell.get_actor()) { - me.type = TILE_TYPES.popdown_floor_visible; - } - }, - on_approach(me, level, other) { - level.transmute_tile(me, 'popdown_floor_visible'); - }, - }, - popdown_floor_visible: { - layer: LAYERS.terrain, - blocks_collision: COLLISION.block_cc1 | COLLISION.block_cc2, - on_depart(me, level, other) { - if (! me.cell.get_item()) { - level.transmute_tile(me, 'popdown_floor'); + visual_state(me) { + if (me && me.cell && (me.cell.get_item() || me.cell.get_actor())) { + return 'depressed'; } + return 'normal'; }, }, no_player1_sign: { @@ -1015,6 +1011,35 @@ const TILE_TYPES = { level._set_tile_prop(me, 'arrows', new_arrows); }, }, + glass_block: { + layer: LAYERS.actor, + collision_mask: COLLISION.block_cc2, + blocks_collision: COLLISION.all, + is_actor: true, + is_block: true, + can_reveal_walls: true, + can_reverse_on_railroad: true, + movement_speed: 4, + allows_push(me, direction) { + return me.arrows && me.arrows.has(direction); + }, + pushes: { + dirt_block: true, + ice_block: true, + frame_block: true, + }, + on_clone(me, original) { + me.arrows = new Set(original.arrows); + }, + on_rotate(me, level, turn) { + // We rotate when turned on railroads + let new_arrows = new Set; + for (let arrow of me.arrows) { + new_arrows.add(DIRECTIONS[arrow][turn]); + } + level._set_tile_prop(me, 'arrows', new_arrows); + }, + }, green_floor: { layer: LAYERS.terrain, on_gray_button(me, level) { @@ -1240,6 +1265,12 @@ const TILE_TYPES = { // No need to do anything, we just need this here as a signal that our .powered_edges // needs to be updated }, + // FIXME don't animate when inactive, but that required inspecting the level! + /* + visual_state(me) { + return this._is_active(me) ? 'active' : 'inactive'; + }, + */ }, teleport_blue: { layer: LAYERS.terrain, @@ -1361,10 +1392,10 @@ const TILE_TYPES = { } } }, - // TODO inactive ones don't animate; transmogrifiers too + // FIXME don't animate when inactive, but that required inspecting the level! /* visual_state(me) { - return this._is_active(me) ? 'active' : 'inactive'; + return me && this._is_active(me) ? 'active' : 'inactive'; }, */ }, @@ -1475,6 +1506,7 @@ const TILE_TYPES = { on_depart(me, level, other) { level.sfx.play_once('button-release', me.cell); }, + visual_state: button_visual_state, }, button_yellow: { layer: LAYERS.terrain, @@ -1520,6 +1552,7 @@ const TILE_TYPES = { on_depart(me, level, other) { level.sfx.play_once('button-release', me.cell); }, + visual_state: button_visual_state, }, button_brown: { layer: LAYERS.terrain, @@ -1551,6 +1584,7 @@ const TILE_TYPES = { trap.type.remove_press(trap, level); } }, + visual_state: button_visual_state, }, button_red: { layer: LAYERS.terrain, @@ -1567,6 +1601,7 @@ const TILE_TYPES = { on_depart(me, level, other) { level.sfx.play_once('button-release', me.cell); }, + visual_state: button_visual_state, }, button_orange: { layer: LAYERS.terrain, @@ -1592,6 +1627,7 @@ const TILE_TYPES = { me.type._toggle_flame_jet(me, level, other); }, + visual_state: button_visual_state, }, button_pink: { layer: LAYERS.terrain, @@ -1613,6 +1649,7 @@ const TILE_TYPES = { on_depart(me, level, other) { level.sfx.play_once('button-release', me.cell); }, + visual_state: button_visual_state, }, button_black: { layer: LAYERS.terrain, @@ -1634,6 +1671,7 @@ const TILE_TYPES = { on_depart(me, level, other) { level.sfx.play_once('button-release', me.cell); }, + visual_state: button_visual_state, }, button_gray: { layer: LAYERS.terrain, @@ -1658,6 +1696,7 @@ const TILE_TYPES = { on_depart(me, level, other) { level.sfx.play_once('button-release', me.cell); }, + visual_state: button_visual_state, }, // Logic gates, all consolidated into a single tile type logic_gate: { diff --git a/tileset-lexy.png b/tileset-lexy.png index f70ab7c..c8af582 100644 Binary files a/tileset-lexy.png and b/tileset-lexy.png differ diff --git a/tileset-src/tileset-lexy.aseprite b/tileset-src/tileset-lexy.aseprite index 645b80b..c6101cf 100644 Binary files a/tileset-src/tileset-lexy.aseprite and b/tileset-src/tileset-lexy.aseprite differ