Merge thin walls into a single tile; split "overlay" layer into correct CC2 parts
This commit is contained in:
parent
1c5f63b61b
commit
b9a311a18c
@ -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 = {
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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)
|
||||
|
||||
104
js/tileset.js
104
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]);
|
||||
}
|
||||
|
||||
// Wired tiles may also have tunnels, drawn on top of everything else
|
||||
if (drawspec.wired && tile && tile.wire_tunnel_directions) {
|
||||
|
||||
@ -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,
|
||||
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';
|
||||
},
|
||||
thinwall_s: {
|
||||
draw_layer: DRAW_LAYERS.overlay,
|
||||
thin_walls: new Set(['south']),
|
||||
blocks_leaving: blocks_leaving_thin_walls,
|
||||
blocks_leaving(me, actor, direction) {
|
||||
return ((me.edges & DIRECTIONS[direction].bit) !== 0) && actor.type.name !== 'ghost';
|
||||
},
|
||||
thinwall_e: {
|
||||
draw_layer: DRAW_LAYERS.overlay,
|
||||
thin_walls: new Set(['east']),
|
||||
blocks_leaving: blocks_leaving_thin_walls,
|
||||
populate_defaults(me) {
|
||||
me.edges = 0; // bitmask of directions
|
||||
},
|
||||
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: {
|
||||
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,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user