diff --git a/js/defs.js b/js/defs.js index 735fbe8..4aa20e6 100644 --- a/js/defs.js +++ b/js/defs.js @@ -57,14 +57,16 @@ export const INPUT_BITS = { wait: 0x8000, }; -// TODO cc2 order is: swivel, thinwalls, canopy (and yes you can have them all in the same tile) export const DRAW_LAYERS = { terrain: 0, item: 1, item_mod: 2, actor: 3, - overlay: 4, - MAX: 5, + vfx: 4, + swivel: 5, + thin_wall: 6, + canopy: 7, + MAX: 8, }; export const COLLISION = { diff --git a/js/format-c2g.js b/js/format-c2g.js index dbd4c6a..4b8756e 100644 --- a/js/format-c2g.js +++ b/js/format-c2g.js @@ -216,16 +216,46 @@ const TILE_ENCODING = { extra_args: [arg_direction], }, 0x1b: { - name: 'thinwall_s', + // CC1 south thin wall + name: 'thin_walls', has_next: true, + modifier: { + dummy: true, + decode(tile, mod) { + tile.edges = DIRECTIONS['south'].bit; + }, + encode(tile) { + return 0; + }, + }, }, 0x1c: { - name: 'thinwall_e', + // CC1 east thin wall + name: 'thin_walls', has_next: true, + modifier: { + dummy: true, + decode(tile, mod) { + tile.edges = DIRECTIONS['east'].bit; + }, + encode(tile) { + return 0; + }, + }, }, 0x1d: { - name: 'thinwall_se', + // CC1 southeast thin wall + name: 'thin_walls', has_next: true, + modifier: { + dummy: true, + decode(tile, mod) { + tile.edges = DIRECTIONS['south'].bit | DIRECTIONS['east'].bit; + }, + encode(tile) { + return 0; + }, + }, }, 0x1e: { name: 'gravel', @@ -1042,21 +1072,11 @@ export function parse_level(buf, number = 1) { // bitmask let mask = bytes[p]; p++; - // This order is important; this is the order CC2 draws them in if (mask & 0x10) { cell.push({type: TILE_TYPES['canopy']}); } - if (mask & 0x08) { - cell.push({type: TILE_TYPES['thinwall_w']}); - } - if (mask & 0x04) { - cell.push({type: TILE_TYPES['thinwall_s']}); - } - if (mask & 0x02) { - cell.push({type: TILE_TYPES['thinwall_e']}); - } - if (mask & 0x01) { - cell.push({type: TILE_TYPES['thinwall_n']}); + if (mask & 0x0f) { + cell.push({type: TILE_TYPES['thin_walls'], 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 diff --git a/js/format-dat.js b/js/format-dat.js index 3821b10..f322448 100644 --- a/js/format-dat.js +++ b/js/format-dat.js @@ -1,3 +1,4 @@ +import { DIRECTIONS } from './defs.js'; import * as format_base from './format-base.js'; import TILE_TYPES from './tiletypes.js'; import * as util from './util.js'; @@ -9,10 +10,10 @@ const TILE_ENCODING = { 0x03: 'water', 0x04: 'fire', 0x05: 'wall_invisible', - 0x06: 'thinwall_n', - 0x07: 'thinwall_w', - 0x08: 'thinwall_s', - 0x09: 'thinwall_e', + 0x06: ['thin_walls', {edges: DIRECTIONS['north'].bit}], + 0x07: ['thin_walls', {edges: DIRECTIONS['west'].bit}], + 0x08: ['thin_walls', {edges: DIRECTIONS['south'].bit}], + 0x09: ['thin_walls', {edges: DIRECTIONS['east'].bit}], // This is MSCC's incomprehensible non-directional dirt block, which needs a direction for Lynx // purposes; Tile World defaults it to north 0x0a: ['dirt_block', 'north'], @@ -54,7 +55,7 @@ const TILE_ENCODING = { 0x2d: 'gravel', 0x2e: 'popwall', 0x2f: 'hint', - 0x30: 'thinwall_se', + 0x30: ['thin_walls', {edges: DIRECTIONS['south'].bit | DIRECTIONS['east'].bit}], 0x31: 'cloner', 0x32: 'force_floor_all', 0x33: 'bogus_player_drowned', @@ -217,14 +218,18 @@ function parse_level(bytes, number) { throw new Error(`Invalid tile byte 0x${tile_byte.toString(16)} at (${x}, ${y})`); } - let name, direction; + let name, extra; if (spec instanceof Array) { - [name, direction] = spec; + [name, extra] = spec; + if (typeof extra === 'string') { + extra = {direction: extra}; + } } else { name = spec; + extra = {}; } - let type = TILE_TYPES[name]; + let tile = {type: TILE_TYPES[name], ...extra}; for (let i = 0; i < count; i++) { if (c >= 1024) @@ -242,7 +247,7 @@ function parse_level(bytes, number) { continue; } - cell.unshift({type, direction}); + cell.unshift({...tile}); } } if (c !== 1024) diff --git a/js/tileset.js b/js/tileset.js index cc86093..c635f6a 100644 --- a/js/tileset.js +++ b/js/tileset.js @@ -18,7 +18,6 @@ import TILE_TYPES from './tiletypes.js'; // special features i currently have // - directions for actors, can be used anywhere // - arrows: for directional blocks -// - mask: for thin walls (though the idea is useful in many more places) // - wired: for wired tiles // - overlay: for green/purple walls mostly, also some bogus cc1 tiles @@ -246,27 +245,11 @@ export const CC2_TILESET_LAYOUT = { hidden: [0, 10], revealed: [10, 31], }, - // Thin walls are built piecemeal from these two tiles; the first is N/S, - // the second is E/W - thinwall_n: { - tile: [1, 10], - mask: [0, 0, 1, 0.5], - }, - thinwall_s: { - tile: [1, 10], - mask: [0, 0.5, 1, 0.5], - }, - thinwall_w: { - tile: [2, 10], - mask: [0, 0, 0.5, 1], - }, - thinwall_e: { - tile: [2, 10], - mask: [0.5, 0, 0.5, 1], - }, - thinwall_se: { - base: 'thinwall_s', - overlay: 'thinwall_e', + // Thin walls are built piecemeal from two tiles; the first is N/S, the second is E/W + thin_walls: { + special: 'thin_walls', + thin_walls_ns: [1, 10], + thin_walls_ew: [2, 10], }, // TODO directional block arrows teleport_blue: { @@ -648,10 +631,15 @@ export const TILE_WORLD_TILESET_LAYOUT = { fire: [0, 4], wall_invisible: [0, 5], wall_invisible_revealed: [0, 1], - thinwall_n: [0, 6], - thinwall_w: [0, 7], - thinwall_s: [0, 8], - thinwall_e: [0, 9], + // FIXME in cc1 tilesets these are opaque so they should draw at the terrain layer + thin_walls: { + special: 'thin_walls_cc1', + north: [0, 6], + west: [0, 7], + south: [0, 8], + east: [0, 9], + southeast: [3, 0], + }, // This is the non-directed dirt block, which we don't have // dirt_block: [0, 10], dirt: [0, 11], @@ -700,7 +688,6 @@ export const TILE_WORLD_TILESET_LAYOUT = { popwall2: [2, 14], hint: [2, 15], - thinwall_se: [3, 0], cloner: [3, 1], force_floor_all: [3, 2], splash: [3, 3], @@ -988,6 +975,46 @@ export class Tileset { } } + _draw_thin_walls(drawspec, tile, tic, blit) { + 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) { + blit(...drawspec.thin_walls_ns, 0, 0, 1, 0.5); + } + if (edges & DIRECTIONS['east'].bit) { + blit(...drawspec.thin_walls_ew, 0.5, 0, 0.5, 1); + } + if (edges & DIRECTIONS['south'].bit) { + blit(...drawspec.thin_walls_ns, 0, 0.5, 1, 0.5); + } + if (edges & DIRECTIONS['west'].bit) { + blit(...drawspec.thin_walls_ew, 0, 0, 0.5, 1); + } + } + + _draw_thin_walls_cc1(drawspec, tile, tic, blit) { + 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)) { + blit(...drawspec.southeast); + } + else if (edges & DIRECTIONS['north'].bit) { + blit(...drawspec.north); + } + else if (edges & DIRECTIONS['east'].bit) { + blit(...drawspec.east); + } + else if (edges & DIRECTIONS['south'].bit) { + blit(...drawspec.south); + } + else { + blit(...drawspec.west); + } + } + _draw_logic_gate(drawspec, tile, tic, blit) { // Layer 1: wiring state // Always draw the unpowered wire base @@ -1138,6 +1165,14 @@ export class Tileset { this._draw_letter(drawspec, tile, tic, blit); return; } + else if (drawspec.special === 'thin_walls') { + this._draw_thin_walls(drawspec, tile, tic, blit); + return; + } + else if (drawspec.special === 'thin_walls_cc1') { + this._draw_thin_walls_cc1(drawspec, tile, tic, blit); + return; + } else if (drawspec.special === 'perception') { if (perception >= drawspec.threshold) { drawspec = drawspec.revealed; @@ -1157,12 +1192,7 @@ export class Tileset { } let coords = drawspec; - if (drawspec.mask) { - // Some tiles (OK, just the thin walls) don't actually draw a full - // tile, so some adjustments are needed; see below - coords = drawspec.tile; - } - else if (drawspec.wired) { + 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 @@ -1282,16 +1312,8 @@ export class Tileset { } } - if (drawspec.mask) { - // Continue on with masking - coords = drawspec.tile; - let [x0, y0, w, h] = drawspec.mask; - blit(coords[0], coords[1], x0, y0, w, h); - } - else { - if (!coords) console.error(name, tile); - blit(coords[0], coords[1]); - } + if (!coords) console.error(name, tile); + blit(coords[0], coords[1]); // Wired tiles may also have tunnels, drawn on top of everything else if (drawspec.wired && tile && tile.wire_tunnel_directions) { diff --git a/js/tiletypes.js b/js/tiletypes.js index 7849897..5fa8a24 100644 --- a/js/tiletypes.js +++ b/js/tiletypes.js @@ -283,30 +283,17 @@ const TILE_TYPES = { }, }, // FIXME in a cc1 tileset, these tiles are opaque >:S - thinwall_n: { - draw_layer: DRAW_LAYERS.overlay, - thin_walls: new Set(['north']), - blocks_leaving: blocks_leaving_thin_walls, - }, - thinwall_s: { - draw_layer: DRAW_LAYERS.overlay, - thin_walls: new Set(['south']), - blocks_leaving: blocks_leaving_thin_walls, - }, - thinwall_e: { - draw_layer: DRAW_LAYERS.overlay, - thin_walls: new Set(['east']), - blocks_leaving: blocks_leaving_thin_walls, - }, - thinwall_w: { - draw_layer: DRAW_LAYERS.overlay, - thin_walls: new Set(['west']), - blocks_leaving: blocks_leaving_thin_walls, - }, - thinwall_se: { - draw_layer: DRAW_LAYERS.overlay, - thin_walls: new Set(['south', 'east']), - blocks_leaving: blocks_leaving_thin_walls, + thin_walls: { + draw_layer: DRAW_LAYERS.thin_wall, + blocks(me, level, actor, direction) { + return ((me.edges & DIRECTIONS[direction].opposite_bit) !== 0) && actor.type.name !== 'ghost'; + }, + blocks_leaving(me, actor, direction) { + return ((me.edges & DIRECTIONS[direction].bit) !== 0) && actor.type.name !== 'ghost'; + }, + populate_defaults(me) { + me.edges = 0; // bitmask of directions + }, }, fake_wall: { draw_layer: DRAW_LAYERS.terrain, @@ -369,7 +356,7 @@ const TILE_TYPES = { blocks_collision: COLLISION.all, }, canopy: { - draw_layer: DRAW_LAYERS.overlay, + draw_layer: DRAW_LAYERS.canopy, blocks_collision: COLLISION.bug | COLLISION.rover, blocks(me, level, other, direction) { // Blobs will specifically not move from one canopy to another @@ -382,9 +369,8 @@ const TILE_TYPES = { swivel_floor: { draw_layer: DRAW_LAYERS.terrain, }, - // TODO thin walls explicitly draw over swivels, so they might have their own layer swivel_ne: { - draw_layer: DRAW_LAYERS.overlay, + draw_layer: DRAW_LAYERS.swivel, thin_walls: new Set(['north', 'east']), on_depart(me, level, other) { if (other.direction === 'north') { @@ -401,7 +387,7 @@ const TILE_TYPES = { on_power: activate_me, }, swivel_se: { - draw_layer: DRAW_LAYERS.overlay, + draw_layer: DRAW_LAYERS.swivel, thin_walls: new Set(['south', 'east']), on_depart(me, level, other) { if (other.direction === 'south') { @@ -418,7 +404,7 @@ const TILE_TYPES = { on_power: activate_me, }, swivel_sw: { - draw_layer: DRAW_LAYERS.overlay, + draw_layer: DRAW_LAYERS.swivel, thin_walls: new Set(['south', 'west']), on_depart(me, level, other) { if (other.direction === 'south') { @@ -435,7 +421,7 @@ const TILE_TYPES = { on_power: activate_me, }, swivel_nw: { - draw_layer: DRAW_LAYERS.overlay, + draw_layer: DRAW_LAYERS.swivel, thin_walls: new Set(['north', 'west']), on_depart(me, level, other) { if (other.direction === 'north') { @@ -2641,7 +2627,7 @@ const TILE_TYPES = { // VFX splash: { - draw_layer: DRAW_LAYERS.overlay, + draw_layer: DRAW_LAYERS.vfx, is_actor: true, collision_mask: 0, blocks_collision: COLLISION.real_player, @@ -2653,7 +2639,7 @@ const TILE_TYPES = { }, }, explosion: { - draw_layer: DRAW_LAYERS.overlay, + draw_layer: DRAW_LAYERS.vfx, is_actor: true, collision_mask: 0, blocks_collision: COLLISION.real_player, @@ -2673,7 +2659,7 @@ const TILE_TYPES = { }, // Custom VFX (identical function, but different aesthetic) splash_slime: { - draw_layer: DRAW_LAYERS.overlay, + draw_layer: DRAW_LAYERS.vfx, is_actor: true, collision_mask: 0, blocks_collision: COLLISION.real_player,