Merge thin walls into a single tile; split "overlay" layer into correct CC2 parts

This commit is contained in:
Eevee (Evelyn Woods) 2020-12-29 19:29:22 -07:00
parent 1c5f63b61b
commit b9a311a18c
5 changed files with 138 additions and 103 deletions

View File

@ -57,14 +57,16 @@ export const INPUT_BITS = {
wait: 0x8000, wait: 0x8000,
}; };
// TODO cc2 order is: swivel, thinwalls, canopy (and yes you can have them all in the same tile)
export const DRAW_LAYERS = { export const DRAW_LAYERS = {
terrain: 0, terrain: 0,
item: 1, item: 1,
item_mod: 2, item_mod: 2,
actor: 3, actor: 3,
overlay: 4, vfx: 4,
MAX: 5, swivel: 5,
thin_wall: 6,
canopy: 7,
MAX: 8,
}; };
export const COLLISION = { export const COLLISION = {

View File

@ -216,16 +216,46 @@ const TILE_ENCODING = {
extra_args: [arg_direction], extra_args: [arg_direction],
}, },
0x1b: { 0x1b: {
name: 'thinwall_s', // CC1 south thin wall
name: 'thin_walls',
has_next: true, has_next: true,
modifier: {
dummy: true,
decode(tile, mod) {
tile.edges = DIRECTIONS['south'].bit;
},
encode(tile) {
return 0;
},
},
}, },
0x1c: { 0x1c: {
name: 'thinwall_e', // CC1 east thin wall
name: 'thin_walls',
has_next: true, has_next: true,
modifier: {
dummy: true,
decode(tile, mod) {
tile.edges = DIRECTIONS['east'].bit;
},
encode(tile) {
return 0;
},
},
}, },
0x1d: { 0x1d: {
name: 'thinwall_se', // CC1 southeast thin wall
name: 'thin_walls',
has_next: true, has_next: true,
modifier: {
dummy: true,
decode(tile, mod) {
tile.edges = DIRECTIONS['south'].bit | DIRECTIONS['east'].bit;
},
encode(tile) {
return 0;
},
},
}, },
0x1e: { 0x1e: {
name: 'gravel', name: 'gravel',
@ -1042,21 +1072,11 @@ export function parse_level(buf, number = 1) {
// bitmask // bitmask
let mask = bytes[p]; let mask = bytes[p];
p++; p++;
// This order is important; this is the order CC2 draws them in
if (mask & 0x10) { if (mask & 0x10) {
cell.push({type: TILE_TYPES['canopy']}); cell.push({type: TILE_TYPES['canopy']});
} }
if (mask & 0x08) { if (mask & 0x0f) {
cell.push({type: TILE_TYPES['thinwall_w']}); cell.push({type: TILE_TYPES['thin_walls'], edges: mask & 0x0f});
}
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']});
} }
// Skip the rest of the loop. That means we don't handle any of the other // 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 // special behavior below, but neither thin walls nor canopies should use

View File

@ -1,3 +1,4 @@
import { DIRECTIONS } from './defs.js';
import * as format_base from './format-base.js'; import * as format_base from './format-base.js';
import TILE_TYPES from './tiletypes.js'; import TILE_TYPES from './tiletypes.js';
import * as util from './util.js'; import * as util from './util.js';
@ -9,10 +10,10 @@ const TILE_ENCODING = {
0x03: 'water', 0x03: 'water',
0x04: 'fire', 0x04: 'fire',
0x05: 'wall_invisible', 0x05: 'wall_invisible',
0x06: 'thinwall_n', 0x06: ['thin_walls', {edges: DIRECTIONS['north'].bit}],
0x07: 'thinwall_w', 0x07: ['thin_walls', {edges: DIRECTIONS['west'].bit}],
0x08: 'thinwall_s', 0x08: ['thin_walls', {edges: DIRECTIONS['south'].bit}],
0x09: 'thinwall_e', 0x09: ['thin_walls', {edges: DIRECTIONS['east'].bit}],
// This is MSCC's incomprehensible non-directional dirt block, which needs a direction for Lynx // This is MSCC's incomprehensible non-directional dirt block, which needs a direction for Lynx
// purposes; Tile World defaults it to north // purposes; Tile World defaults it to north
0x0a: ['dirt_block', 'north'], 0x0a: ['dirt_block', 'north'],
@ -54,7 +55,7 @@ const TILE_ENCODING = {
0x2d: 'gravel', 0x2d: 'gravel',
0x2e: 'popwall', 0x2e: 'popwall',
0x2f: 'hint', 0x2f: 'hint',
0x30: 'thinwall_se', 0x30: ['thin_walls', {edges: DIRECTIONS['south'].bit | DIRECTIONS['east'].bit}],
0x31: 'cloner', 0x31: 'cloner',
0x32: 'force_floor_all', 0x32: 'force_floor_all',
0x33: 'bogus_player_drowned', 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})`); throw new Error(`Invalid tile byte 0x${tile_byte.toString(16)} at (${x}, ${y})`);
} }
let name, direction; let name, extra;
if (spec instanceof Array) { if (spec instanceof Array) {
[name, direction] = spec; [name, extra] = spec;
if (typeof extra === 'string') {
extra = {direction: extra};
}
} }
else { else {
name = spec; name = spec;
extra = {};
} }
let type = TILE_TYPES[name]; let tile = {type: TILE_TYPES[name], ...extra};
for (let i = 0; i < count; i++) { for (let i = 0; i < count; i++) {
if (c >= 1024) if (c >= 1024)
@ -242,7 +247,7 @@ function parse_level(bytes, number) {
continue; continue;
} }
cell.unshift({type, direction}); cell.unshift({...tile});
} }
} }
if (c !== 1024) if (c !== 1024)

View File

@ -18,7 +18,6 @@ import TILE_TYPES from './tiletypes.js';
// special features i currently have // special features i currently have
// - directions for actors, can be used anywhere // - directions for actors, can be used anywhere
// - arrows: for directional blocks // - arrows: for directional blocks
// - mask: for thin walls (though the idea is useful in many more places)
// - wired: for wired tiles // - wired: for wired tiles
// - overlay: for green/purple walls mostly, also some bogus cc1 tiles // - overlay: for green/purple walls mostly, also some bogus cc1 tiles
@ -246,27 +245,11 @@ export const CC2_TILESET_LAYOUT = {
hidden: [0, 10], hidden: [0, 10],
revealed: [10, 31], revealed: [10, 31],
}, },
// Thin walls are built piecemeal from these two tiles; the first is N/S, // Thin walls are built piecemeal from two tiles; the first is N/S, the second is E/W
// the second is E/W thin_walls: {
thinwall_n: { special: 'thin_walls',
tile: [1, 10], thin_walls_ns: [1, 10],
mask: [0, 0, 1, 0.5], thin_walls_ew: [2, 10],
},
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',
}, },
// TODO directional block arrows // TODO directional block arrows
teleport_blue: { teleport_blue: {
@ -648,10 +631,15 @@ export const TILE_WORLD_TILESET_LAYOUT = {
fire: [0, 4], fire: [0, 4],
wall_invisible: [0, 5], wall_invisible: [0, 5],
wall_invisible_revealed: [0, 1], wall_invisible_revealed: [0, 1],
thinwall_n: [0, 6], // FIXME in cc1 tilesets these are opaque so they should draw at the terrain layer
thinwall_w: [0, 7], thin_walls: {
thinwall_s: [0, 8], special: 'thin_walls_cc1',
thinwall_e: [0, 9], 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 // This is the non-directed dirt block, which we don't have
// dirt_block: [0, 10], // dirt_block: [0, 10],
dirt: [0, 11], dirt: [0, 11],
@ -700,7 +688,6 @@ export const TILE_WORLD_TILESET_LAYOUT = {
popwall2: [2, 14], popwall2: [2, 14],
hint: [2, 15], hint: [2, 15],
thinwall_se: [3, 0],
cloner: [3, 1], cloner: [3, 1],
force_floor_all: [3, 2], force_floor_all: [3, 2],
splash: [3, 3], 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) { _draw_logic_gate(drawspec, tile, tic, blit) {
// Layer 1: wiring state // Layer 1: wiring state
// Always draw the unpowered wire base // Always draw the unpowered wire base
@ -1138,6 +1165,14 @@ export class Tileset {
this._draw_letter(drawspec, tile, tic, blit); this._draw_letter(drawspec, tile, tic, blit);
return; 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') { else if (drawspec.special === 'perception') {
if (perception >= drawspec.threshold) { if (perception >= drawspec.threshold) {
drawspec = drawspec.revealed; drawspec = drawspec.revealed;
@ -1157,12 +1192,7 @@ export class Tileset {
} }
let coords = drawspec; let coords = drawspec;
if (drawspec.mask) { if (drawspec.wired) {
// 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) {
// This /should/ match CC2's draw order exactly, based on experimentation // This /should/ match CC2's draw order exactly, based on experimentation
let wire_radius = this.layout['#wire-width'] / 2; let wire_radius = this.layout['#wire-width'] / 2;
// TODO circuit block with a lightning bolt is always powered // 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); if (!coords) console.error(name, tile);
blit(coords[0], coords[1]); blit(coords[0], coords[1]);
}
// Wired tiles may also have tunnels, drawn on top of everything else // Wired tiles may also have tunnels, drawn on top of everything else
if (drawspec.wired && tile && tile.wire_tunnel_directions) { if (drawspec.wired && tile && tile.wire_tunnel_directions) {

View File

@ -283,30 +283,17 @@ const TILE_TYPES = {
}, },
}, },
// FIXME in a cc1 tileset, these tiles are opaque >:S // FIXME in a cc1 tileset, these tiles are opaque >:S
thinwall_n: { thin_walls: {
draw_layer: DRAW_LAYERS.overlay, draw_layer: DRAW_LAYERS.thin_wall,
thin_walls: new Set(['north']), blocks(me, level, actor, direction) {
blocks_leaving: blocks_leaving_thin_walls, return ((me.edges & DIRECTIONS[direction].opposite_bit) !== 0) && actor.type.name !== 'ghost';
}, },
thinwall_s: { blocks_leaving(me, actor, direction) {
draw_layer: DRAW_LAYERS.overlay, return ((me.edges & DIRECTIONS[direction].bit) !== 0) && actor.type.name !== 'ghost';
thin_walls: new Set(['south']),
blocks_leaving: blocks_leaving_thin_walls,
}, },
thinwall_e: { populate_defaults(me) {
draw_layer: DRAW_LAYERS.overlay, me.edges = 0; // bitmask of directions
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,
}, },
fake_wall: { fake_wall: {
draw_layer: DRAW_LAYERS.terrain, draw_layer: DRAW_LAYERS.terrain,
@ -369,7 +356,7 @@ const TILE_TYPES = {
blocks_collision: COLLISION.all, blocks_collision: COLLISION.all,
}, },
canopy: { canopy: {
draw_layer: DRAW_LAYERS.overlay, draw_layer: DRAW_LAYERS.canopy,
blocks_collision: COLLISION.bug | COLLISION.rover, blocks_collision: COLLISION.bug | COLLISION.rover,
blocks(me, level, other, direction) { blocks(me, level, other, direction) {
// Blobs will specifically not move from one canopy to another // Blobs will specifically not move from one canopy to another
@ -382,9 +369,8 @@ const TILE_TYPES = {
swivel_floor: { swivel_floor: {
draw_layer: DRAW_LAYERS.terrain, draw_layer: DRAW_LAYERS.terrain,
}, },
// TODO thin walls explicitly draw over swivels, so they might have their own layer
swivel_ne: { swivel_ne: {
draw_layer: DRAW_LAYERS.overlay, draw_layer: DRAW_LAYERS.swivel,
thin_walls: new Set(['north', 'east']), thin_walls: new Set(['north', 'east']),
on_depart(me, level, other) { on_depart(me, level, other) {
if (other.direction === 'north') { if (other.direction === 'north') {
@ -401,7 +387,7 @@ const TILE_TYPES = {
on_power: activate_me, on_power: activate_me,
}, },
swivel_se: { swivel_se: {
draw_layer: DRAW_LAYERS.overlay, draw_layer: DRAW_LAYERS.swivel,
thin_walls: new Set(['south', 'east']), thin_walls: new Set(['south', 'east']),
on_depart(me, level, other) { on_depart(me, level, other) {
if (other.direction === 'south') { if (other.direction === 'south') {
@ -418,7 +404,7 @@ const TILE_TYPES = {
on_power: activate_me, on_power: activate_me,
}, },
swivel_sw: { swivel_sw: {
draw_layer: DRAW_LAYERS.overlay, draw_layer: DRAW_LAYERS.swivel,
thin_walls: new Set(['south', 'west']), thin_walls: new Set(['south', 'west']),
on_depart(me, level, other) { on_depart(me, level, other) {
if (other.direction === 'south') { if (other.direction === 'south') {
@ -435,7 +421,7 @@ const TILE_TYPES = {
on_power: activate_me, on_power: activate_me,
}, },
swivel_nw: { swivel_nw: {
draw_layer: DRAW_LAYERS.overlay, draw_layer: DRAW_LAYERS.swivel,
thin_walls: new Set(['north', 'west']), thin_walls: new Set(['north', 'west']),
on_depart(me, level, other) { on_depart(me, level, other) {
if (other.direction === 'north') { if (other.direction === 'north') {
@ -2641,7 +2627,7 @@ const TILE_TYPES = {
// VFX // VFX
splash: { splash: {
draw_layer: DRAW_LAYERS.overlay, draw_layer: DRAW_LAYERS.vfx,
is_actor: true, is_actor: true,
collision_mask: 0, collision_mask: 0,
blocks_collision: COLLISION.real_player, blocks_collision: COLLISION.real_player,
@ -2653,7 +2639,7 @@ const TILE_TYPES = {
}, },
}, },
explosion: { explosion: {
draw_layer: DRAW_LAYERS.overlay, draw_layer: DRAW_LAYERS.vfx,
is_actor: true, is_actor: true,
collision_mask: 0, collision_mask: 0,
blocks_collision: COLLISION.real_player, blocks_collision: COLLISION.real_player,
@ -2673,7 +2659,7 @@ const TILE_TYPES = {
}, },
// Custom VFX (identical function, but different aesthetic) // Custom VFX (identical function, but different aesthetic)
splash_slime: { splash_slime: {
draw_layer: DRAW_LAYERS.overlay, draw_layer: DRAW_LAYERS.vfx,
is_actor: true, is_actor: true,
collision_mask: 0, collision_mask: 0,
blocks_collision: COLLISION.real_player, blocks_collision: COLLISION.real_player,