Rearrange C2M tile definitions to make saving easier

Also, tile templates no longer use name or carry format-specific
modifiers; they have the same properties as real tiles.
This commit is contained in:
Eevee (Evelyn Woods) 2020-09-15 22:45:15 -06:00
parent 325960b609
commit cce28c2d7e
5 changed files with 600 additions and 272 deletions

View File

@ -1,3 +1,4 @@
import { DIRECTIONS } from './defs.js';
import * as util from './format-util.js'; import * as util from './format-util.js';
import TILE_TYPES from './tiletypes.js'; import TILE_TYPES from './tiletypes.js';
@ -65,147 +66,538 @@ class CC2Demo {
} }
function parse_modifier_wire(tile, modifier) { let modifier_wire = {
decode(tile, modifier) {
tile.wire_directions = modifier & 0x0f; tile.wire_directions = modifier & 0x0f;
tile.wire_tunnel_directions = (modifier & 0xf0) >> 4; tile.wire_tunnel_directions = (modifier & 0xf0) >> 4;
} },
};
let arg_direction = {
size: 1,
decode(tile, dirbyte) {
let direction = ['north', 'east', 'south', 'west'][dirbyte & 0x03];
tile.direction = direction;
},
};
// TODO assert that direction + next match the tile types // TODO assert that direction + next match the tile types
const TILE_ENCODING = { const TILE_ENCODING = {
0x01: ['floor', parse_modifier_wire], 0x01: {
0x02: 'wall', name: 'floor',
0x03: 'ice', modifier: modifier_wire,
0x04: 'ice_sw', },
0x05: 'ice_nw', 0x02: {
0x06: 'ice_ne', name: 'wall',
0x07: 'ice_se', },
0x08: 'water', 0x03: {
0x09: 'fire', name: 'ice',
0x0a: 'force_floor_n', },
0x0b: 'force_floor_e', 0x04: {
0x0c: 'force_floor_s', name: 'ice_sw',
0x0d: 'force_floor_w', },
0x0e: 'green_wall', 0x05: {
0x0f: 'green_floor', name: 'ice_nw',
0x10: ['teleport_red', parse_modifier_wire], },
0x11: ['teleport_blue', parse_modifier_wire], 0x06: {
0x12: 'teleport_yellow', name: 'ice_ne',
0x13: 'teleport_green', },
0x14: 'exit', 0x07: {
0x15: 'slime', name: 'ice_se',
0x16: ['player', '#direction', '#next'], },
0x17: ['dirt_block', '#direction', '#next'], 0x08: {
0x18: ['walker', '#direction', '#next'], name: 'water',
0x19: ['glider', '#direction', '#next'], },
0x1a: ['ice_block', '#direction', '#next'], 0x09: {
0x1b: ['thinwall_s', '#next'], name: 'fire',
0x1c: ['thinwall_e', '#next'], },
0x1d: ['thinwall_se', '#next'], 0x0a: {
0x1e: 'gravel', name: 'force_floor_n',
0x1f: 'button_green', },
0x20: 'button_blue', 0x0b: {
0x21: ['tank_blue', '#direction', '#next'], name: 'force_floor_e',
0x22: 'door_red', },
0x23: 'door_blue', 0x0c: {
0x24: 'door_yellow', name: 'force_floor_s',
0x25: 'door_green', },
0x26: ['key_red', '#next'], 0x0d: {
0x27: ['key_blue', '#next'], name: 'force_floor_w',
0x28: ['key_yellow', '#next'], },
0x29: ['key_green', '#next'], 0x0e: {
0x2a: ['chip', '#next'], name: 'green_wall',
0x2b: ['chip_extra', '#next'], },
0x2c: 'socket', 0x0f: {
0x2d: 'popwall', name: 'green_floor',
0x2e: 'wall_appearing', },
0x2f: 'wall_invisible', 0x10: {
0x30: 'fake_wall', name: 'teleport_red',
0x31: 'fake_floor', modifier: modifier_wire,
0x32: 'dirt', },
0x33: ['bug', '#direction', '#next'], 0x11: {
0x34: ['paramecium', '#direction', '#next'], name: 'teleport_blue',
0x35: ['ball', '#direction', '#next'], modifier: modifier_wire,
0x36: ['blob', '#direction', '#next'], },
0x37: ['teeth', '#direction', '#next'], 0x12: {
0x38: ['fireball', '#direction', '#next'], name: 'teleport_yellow',
0x39: 'button_red', },
0x3a: 'button_brown', 0x13: {
0x3b: ['cleats', '#next'], name: 'teleport_green',
0x3c: ['suction_boots', '#next'], },
0x3d: ['fire_boots', '#next'], 0x14: {
0x3e: ['flippers', '#next'], name: 'exit',
0x3f: 'thief_tools', },
0x40: ['bomb', '#next'], 0x15: {
name: 'slime',
},
0x16: {
name: 'player',
has_next: true,
extra_args: [arg_direction],
},
0x17: {
name: 'dirt_block',
has_next: true,
extra_args: [arg_direction],
},
0x18: {
name: 'walker',
has_next: true,
extra_args: [arg_direction],
},
0x19: {
name: 'glider',
has_next: true,
extra_args: [arg_direction],
},
0x1a: {
name: 'ice_block',
has_next: true,
extra_args: [arg_direction],
},
0x1b: {
name: 'thinwall_s',
has_next: true,
},
0x1c: {
name: 'thinwall_e',
has_next: true,
},
0x1d: {
name: 'thinwall_se',
has_next: true,
},
0x1e: {
name: 'gravel',
},
0x1f: {
name: 'button_green',
},
0x20: {
name: 'button_blue',
},
0x21: {
name: 'tank_blue',
has_next: true,
extra_args: [arg_direction],
},
0x22: {
name: 'door_red',
},
0x23: {
name: 'door_blue',
},
0x24: {
name: 'door_yellow',
},
0x25: {
name: 'door_green',
},
0x26: {
name: 'key_red',
has_next: true,
},
0x27: {
name: 'key_blue',
has_next: true,
},
0x28: {
name: 'key_yellow',
has_next: true,
},
0x29: {
name: 'key_green',
has_next: true,
},
0x2a: {
name: 'chip',
has_next: true,
},
0x2b: {
name: 'chip_extra',
has_next: true,
},
0x2c: {
name: 'socket',
},
0x2d: {
name: 'popwall',
},
0x2e: {
name: 'wall_appearing',
},
0x2f: {
name: 'wall_invisible',
},
0x30: {
name: 'fake_wall',
},
0x31: {
name: 'fake_floor',
},
0x32: {
name: 'dirt',
},
0x33: {
name: 'bug',
has_next: true,
extra_args: [arg_direction],
},
0x34: {
name: 'paramecium',
has_next: true,
extra_args: [arg_direction],
},
0x35: {
name: 'ball',
has_next: true,
extra_args: [arg_direction],
},
0x36: {
name: 'blob',
has_next: true,
extra_args: [arg_direction],
},
0x37: {
name: 'teeth',
has_next: true,
extra_args: [arg_direction],
},
0x38: {
name: 'fireball',
has_next: true,
extra_args: [arg_direction],
},
0x39: {
name: 'button_red',
},
0x3a: {
name: 'button_brown',
},
0x3b: {
name: 'cleats',
has_next: true,
},
0x3c: {
name: 'suction_boots',
has_next: true,
},
0x3d: {
name: 'fire_boots',
has_next: true,
},
0x3e: {
name: 'flippers',
has_next: true,
},
0x3f: {
name: 'thief_tools',
},
0x40: {
name: 'bomb',
has_next: true,
},
//0x41: Open trap (unused in main levels) : //0x41: Open trap (unused in main levels) :
0x42: 'trap', 0x42: {
0x43: 'cloner', name: 'trap',
0x44: ['#mod', 'cloner'], },
0x45: 'hint', 0x43: {
0x46: 'force_floor_all', name: 'cloner',
},
0x44: {
name: 'cloner',
// TODO visual directions bitmask, no gameplay impact, possible editor impact
modifier: null,
},
0x45: {
name: 'hint',
},
0x46: {
name: 'force_floor_all',
},
// 0x47: 'button_gray', // 0x47: 'button_gray',
0x48: ['swivel_sw', 'swivel_floor'], // FIXME swivel floors... argh...
0x49: ['swivel_nw', 'swivel_floor'], 0x48: {
0x4a: ['swivel_ne', 'swivel_floor'], name: 'swivel_sw',
0x4b: ['swivel_se', 'swivel_floor'], },
0x4c: ['stopwatch_bonus', '#next'], 0x49: {
0x4d: ['stopwatch_toggle', '#next'], name: 'swivel_nw',
0x4e: ['transmogrifier', parse_modifier_wire], },
0x4f: ['#mod', 'railroad'], 0x4a: {
0x50: ['steel', parse_modifier_wire], name: 'swivel_ne',
0x51: ['dynamite', '#next'], },
0x52: ['helmet', '#next'], 0x4b: {
0x56: ['player2', '#direction', '#next'], name: 'swivel_se',
},
0x4c: {
name: 'stopwatch_bonus',
has_next: true,
},
0x4d: {
name: 'stopwatch_toggle',
has_next: true,
},
0x4e: {
name: 'transmogrifier',
modifier: modifier_wire,
},
0x4f: {
name: 'railroad',
modifier: {
decode(tile, mask) {
// TODO railroad props
},
},
},
0x50: {
name: 'steel',
modifier: modifier_wire,
},
0x51: {
name: 'dynamite',
has_next: true,
},
0x52: {
name: 'helmet',
has_next: true,
},
0x56: {
name: 'player2',
has_next: true,
extra_args: [arg_direction],
},
// 0x57: Timid teeth : '#direction', '#next' // 0x57: Timid teeth : '#direction', '#next'
// 0x58: Explosion animation (unused in main levels) : '#direction', '#next' // 0x58: Explosion animation (unused in main levels) : '#direction', '#next'
0x59: ['hiking_boots', '#next'], 0x59: {
0x5a: ['no_player2_sign'], name: 'hiking_boots',
0x5b: ['no_player1_sign'], has_next: true,
},
0x5a: {
name: 'no_player2_sign',
},
0x5b: {
name: 'no_player1_sign',
},
// 0x5c: Inverter gate (N) : Modifier allows other gates, see below // 0x5c: Inverter gate (N) : Modifier allows other gates, see below
0x5e: ['button_pink', parse_modifier_wire], 0x5e: {
0x5f: 'flame_jet_off', name: 'button_pink',
0x60: 'flame_jet_on', modifier: modifier_wire,
0x61: 'button_orange', },
0x5f: {
name: 'flame_jet_off',
},
0x60: {
name: 'flame_jet_on',
},
0x61: {
name: 'button_orange',
},
// 0x62: Lightning bolt : '#next' // 0x62: Lightning bolt : '#next'
0x63: ['tank_yellow', '#direction', '#next'], 0x63: {
0x64: 'button_yellow', name: 'tank_yellow',
has_next: true,
extra_args: [arg_direction],
},
0x64: {
name: 'button_yellow',
},
// 0x65: Mirror Chip : '#direction', '#next' // 0x65: Mirror Chip : '#direction', '#next'
// 0x66: Mirror Melinda : '#direction', '#next' // 0x66: Mirror Melinda : '#direction', '#next'
0x68: ['bowling_ball', '#next'], 0x68: {
0x69: ['rover', '#direction', '#next'], name: 'bowling_ball',
0x6a: ['stopwatch_penalty', '#next'], has_next: true,
0x6b: ['#mod', ['floor_custom_green', 'floor_custom_pink', 'floor_custom_yellow', 'floor_custom_blue']], },
0x6d: ['#thinwall/canopy', '#next'], 0x69: {
0x6f: ['railroad_sign', '#next'], name: 'rover',
0x70: ['#mod', ['wall_custom_green', 'wall_custom_pink', 'wall_custom_yellow', 'wall_custom_blue']], has_next: true,
0x71: ['#mod', 'floor_letter'], extra_args: [arg_direction],
0x72: 'purple_wall', },
0x73: 'purple_floor', 0x6a: {
0x76: ['#mod8', '#next'], name: 'stopwatch_penalty',
0x77: ['#mod16', '#next'], has_next: true,
0x78: ['#mod32', '#next'], },
0x7a: ['score_10', '#next'], 0x6b: {
0x7b: ['score_100', '#next'], name: ['floor_custom_green', 'floor_custom_pink', 'floor_custom_yellow', 'floor_custom_blue'],
0x7c: ['score_1000', '#next'], },
0x7d: ['popdown_wall'], 0x6d: {
0x7e: ['popdown_floor'], // TODO oh this one is probably gonna be hard
0x7f: ['forbidden', '#next'], name: '#thinwall/canopy',
0x80: ['score_2x', '#next'], has_next: true,
0x81: ['directional_block', '#direction', '#directional_block_mask', '#next'], },
0x82: ['floor_mimic', '#direction', '#next'], 0x6f: {
0x83: ['green_bomb', '#next'], name: 'railroad_sign',
0x84: ['green_chip', '#next'], has_next: true,
0x87: ['button_black', parse_modifier_wire], },
0x70: {
name: ['wall_custom_green', 'wall_custom_pink', 'wall_custom_yellow', 'wall_custom_blue'],
},
0x71: {
name: 'floor_letter',
modifier: {
decode(tile, ascii_code) {
tile.ascii_code = ascii_code;
},
},
},
0x72: {
name: 'purple_wall',
},
0x73: {
name: 'purple_floor',
},
0x76: {
name: '#mod8',
},
0x77: {
name: '#mod16',
},
0x78: {
name: '#mod32',
},
0x7a: {
name: 'score_10',
has_next: true,
},
0x7b: {
name: 'score_100',
has_next: true,
},
0x7c: {
name: 'score_1000',
has_next: true,
},
0x7d: {
name: 'popdown_wall',
},
0x7e: {
name: 'popdown_floor',
},
0x7f: {
name: 'forbidden',
has_next: true,
},
0x80: {
name: 'score_2x',
has_next: true,
},
0x81: {
name: 'directional_block',
extra_args: [
arg_direction,
{
size: 1,
decode(tile, mask) {
let arrows = new Set;
for (let [direction, info] of Object.entries(DIRECTIONS)) {
if (mask & info.bit) {
arrows.add(direction);
}
}
tile.arrows = arrows;
},
},
],
has_next: true,
},
0x82: {
name: 'floor_mimic',
has_next: true,
extra_args: [arg_direction],
},
0x83: {
name: 'green_bomb',
has_next: true,
},
0x84: {
name: 'green_chip',
has_next: true,
},
0x87: {
name: 'button_black',
modifier: modifier_wire,
},
// 0x88: ON/OFF switch (OFF) : // 0x88: ON/OFF switch (OFF) :
// 0x89: ON/OFF switch (ON) : // 0x89: ON/OFF switch (ON) :
0x8a: 'thief_keys', 0x8a: {
0x8b: ['ghost', '#direction', '#next'], name: 'thief_keys',
0x8c: ['foil', '#next'], },
0x8d: 'turtle', 0x8b: {
0x8e: ['xray_eye', '#next'], name: 'ghost',
has_next: true,
extra_args: [arg_direction],
},
0x8c: {
name: 'foil',
has_next: true,
},
0x8d: {
name: 'turtle',
},
0x8e: {
name: 'xray_eye',
has_next: true,
},
// 0x8f: Thief bribe : '#next' // 0x8f: Thief bribe : '#next'
// 0x90: Speed boots : '#next' // 0x90: Speed boots : '#next'
// 0x92: Hook : '#next' // 0x92: Hook : '#next'
}; };
const REVERSE_TILE_ENCODING = {};
for (let [tile_byte, spec] of Object.entries(TILE_ENCODING)) {
spec.tile_byte = tile_byte;
if (spec.name instanceof Array) {
// Custom floor/wall
for (let [i, name] of spec.name.entries()) {
// Copy the spec with a hardcoded modifier
let new_spec = Object.assign({}, spec);
new_spec.name = name;
new_spec.modifier = {
encode(tile) {
return i;
},
};
REVERSE_TILE_ENCODING[name] = new_spec;
}
}
else {
REVERSE_TILE_ENCODING[spec.name] = spec;
}
}
// Read 1, 2, or 4 bytes from a DataView
function read_n_bytes(view, start, n) {
if (n === 1) {
return view.getUint8(start, true);
}
else if (n === 2) {
return view.getUint16(start, true);
}
else if (n === 4) {
return view.getUint32(start, true);
}
else {
throw new Error(`Can't read ${n} bytes`);
}
}
// Decompress the little ad-hoc compression scheme used for both map data and // Decompress the little ad-hoc compression scheme used for both map data and
// solution playback // solution playback
@ -373,157 +765,103 @@ export function parse_level(buf) {
function read_spec() { function read_spec() {
let tile_byte = bytes[p]; let tile_byte = bytes[p];
p++; p++;
if (tile_byte >= 0x77 && tile_byte <= 0x78) {
// XXX handle these modifier "tiles"
p += tile_byte - 0x75;
return [];
}
let spec = TILE_ENCODING[tile_byte]; let spec = TILE_ENCODING[tile_byte];
if (! spec) if (! spec)
throw new Error(`Unrecognized tile type 0x${tile_byte.toString(16)}`); throw new Error(`Unrecognized tile type 0x${tile_byte.toString(16)}`);
let name;
let args = [];
if (spec instanceof Array) {
return spec; return spec;
} }
else {
return [spec];
}
}
for (let n = 0; n < width * height; n++) { for (let n = 0; n < width * height; n++) {
let cell = new util.StoredCell; let cell = new util.StoredCell;
while (true) { while (true) {
let [name, ...args] = read_spec(); let spec = read_spec();
if (name === undefined) continue; // XXX modifier skip hack
// Deal with modifiers // Deal with modifiers
let modifier = 0; // defaults to zero let modifier = 0; // defaults to zero
if (name === '#mod8' || name === '#mod16' || name === '#mod32') { if (spec.name === '#mod8' || spec.name === '#mod16' || spec.name === '#mod32') {
if (name === '#mod8') { if (spec.name === '#mod8') {
modifier = bytes[p]; modifier = bytes[p];
p++; p++;
} }
else if (name === '#mod16') { else if (spec.name === '#mod16') {
modifier = map_view.getUint16(p, true); modifier = map_view.getUint16(p, true);
p += 2; p += 2;
} }
else if (name === '#mod32') { else if (spec.name === '#mod32') {
modifier = map_view.getUint32(p, true); modifier = map_view.getUint32(p, true);
p += 4; p += 4;
} }
[name, ...args] = read_spec(); spec = read_spec();
/* if (! spec.modifier) {
* TODO check for modifier expected and warn? console.warn("Got unexpected modifier for tile:", spec.name);
let mod_marker; }
[mod_marker, name, ...args] = read_spec();
if (mod_marker !== '#mod')
throw new Error(`Expected a tile requiring a modifier; got ${name}`);
*/
} }
if (name === '#mod') { let name = spec.name;
[name, ...args] = args;
}
// Make a tile template, possibly dealing with some special cases // Make a tile template, possibly dealing with some special cases
// FIXME restore this
if (name === '#thinwall/canopy') { if (name === '#thinwall/canopy') {
// Thin walls and the canopy are combined into a single // Thin walls and the canopy are combined into a single byte for some
// byte for some reason; split them apart here. Which // reason; split them apart here. Which ones we get is determined by a
// ones we get is determined by a bitmask // bitmask
let mask = bytes[p]; let mask = bytes[p];
p++; p++;
// This order is important; this is the order CC2 draws them in // This order is important; this is the order CC2 draws them in
if (mask & 0x10) { if (mask & 0x10) {
cell.push({name: 'canopy'}); cell.push({type: TILE_TYPES['canopy']});
} }
if (mask & 0x08) { if (mask & 0x08) {
cell.push({name: 'thinwall_w'}); cell.push({type: TILE_TYPES['thinwall_w']});
} }
if (mask & 0x04) { if (mask & 0x04) {
cell.push({name: 'thinwall_s'}); cell.push({type: TILE_TYPES['thinwall_s']});
} }
if (mask & 0x02) { if (mask & 0x02) {
cell.push({name: 'thinwall_e'}); cell.push({type: TILE_TYPES['thinwall_e']});
} }
if (mask & 0x01) { if (mask & 0x01) {
cell.push({name: 'thinwall_n'}); cell.push({type: TILE_TYPES['thinwall_n']});
} }
// Skip the rest of the loop. That means we don't // Skip the rest of the loop. That means we don't handle any of the other
// handle any of the other special behavior below, but // special behavior below, but neither thin walls nor canopies should use
// neither thin walls nor canopies should use any of // any of it, so that's fine
// it, so that's fine
continue; continue;
} }
else if (name instanceof Array) { else if (name instanceof Array) {
// Custom floors and walls are one of several options, // Custom floors and walls are one of several options, chosen by modifier
// given by an optional modifier
name = name[modifier]; name = name[modifier];
} }
let tile = {name, modifier};
cell.push(tile);
let type = TILE_TYPES[name]; let type = TILE_TYPES[name];
if (!type) console.error(name); if (!type) console.error(name, spec);
let tile = {type};
cell.push(tile);
if (spec.modifier) {
spec.modifier.decode(tile, modifier);
}
if (type.is_required_chip) { if (type.is_required_chip) {
level.chips_required++; level.chips_required++;
} }
if (type.is_hint) { if (type.is_hint) {
// Remember all the hint tiles (in reading order) so we // Remember all the hint tiles (in reading order) so we can map extra hints
// can map extra hints to them later. Don't do it now, // to them later. Don't do it now, since the format doesn't technically
// since the format doesn't technically guarantee that // guarantee that the metadata sections appear before the map data!
// the metadata sections appear before the map data!
hint_tiles.push(tile); hint_tiles.push(tile);
} }
// Handle extra arguments // Handle extra arguments
let has_next = false; if (spec.extra_args) {
for (let arg of args) { for (let argspec of spec.extra_args) {
if (arg === '#direction') { let arg = read_n_bytes(map_view, p, argspec.size);
let dirbyte = bytes[p]; p += argspec.size;
p++; argspec.decode(tile, arg);
let direction = ['north', 'east', 'south', 'west'][dirbyte];
if (! direction) {
console.warn(`'${name}' tile at ${n % width}, ${Math.floor(n / width)} has bogus direction byte ${dirbyte}; defaulting to south`);
direction = 'south';
}
tile.direction = direction;
}
else if (arg === '#next') {
has_next = true;
}
else if (arg === '#directional_block_mask') {
// Direction mask used specifically for the directional block
let mask = bytes[p];
p++;
let arrows = new Set;
if (mask & 0x01) {
arrows.add('north');
}
if (mask & 0x02) {
arrows.add('east');
}
if (mask & 0x04) {
arrows.add('south');
}
if (mask & 0x08) {
arrows.add('west');
}
tile.directional_block_arrows = arrows;
}
else if (arg instanceof Function) {
// Functions handle modifiers
arg(tile, modifier);
}
else {
// Anything else is an implicit next tile, e.g.
// turtles imply water underneath
cell.push({name: arg});
} }
} }
if (! has_next) if (! spec.has_next)
break; break;
} }
cell.reverse(); cell.reverse();
@ -562,3 +900,23 @@ export function parse_level(buf) {
return level; return level;
} }
export function synthesize_level(stored_level) {
add_section('CC2M', '133');
// FIXME well this will not do
let map_bytes = new Uint8Array(1024);
map_bytes[0] = stored_level.size_x;
map_bytes[1] = stored_level.size_y;
let p = 2;
for (let cell of stored_level.linear_cells) {
// TODO can i allocate less here? iterate in reverse, avoid slicing?
// TODO assert that the bottom tile has no next, and all the others do
for (let tile of cell.reverse()) {
let [tile_byte, ...args] = REVERSE_TILE_ENCODING[tile.type.name];
}
}
add_section('MAP ', '');
add_section('END ', '');
}

View File

@ -16,13 +16,11 @@ export class Tile {
} }
static from_template(tile_template) { static from_template(tile_template) {
let type = TILE_TYPES[tile_template.name]; let type = tile_template.type;
if (! type) console.error(tile_template.name); if (! type) console.error(tile_template);
let tile = new this(type, tile_template.direction); let tile = new this(type, tile_template.direction);
if (type.load) { // Copy any extra properties in verbatim
type.load(tile, tile_template); return Object.assign(tile, tile_template);
}
return tile;
} }
// Gives the effective position of an actor in motion, given smooth scrolling // Gives the effective position of an actor in motion, given smooth scrolling
@ -782,7 +780,7 @@ export class Level {
// If we're stepping directly on the player, that kills them too // If we're stepping directly on the player, that kills them too
// TODO this only works because i have the player move first; in lynx the check is the other // TODO this only works because i have the player move first; in lynx the check is the other
// way around // way around
if (goal_cell === this.player_leaving_cell) { if (actor.type.is_monster && goal_cell === this.player_leaving_cell) {
this.fail("Oops! Watch out for creatures!"); this.fail("Oops! Watch out for creatures!");
} }

View File

@ -832,13 +832,13 @@ class Editor extends PrimaryView {
let cell = this.stored_level.cells[y][x]; let cell = this.stored_level.cells[y][x];
for (let tile of cell) { for (let tile of cell) {
// Toggle tiles that go in obvious pairs // Toggle tiles that go in obvious pairs
let other = EDITOR_ADJUST_TOGGLES[tile.name]; let other = EDITOR_ADJUST_TOGGLES[tile.type.name];
if (other) { if (other) {
tile.name = other; tile.type = TILE_TYPES[other];
} }
// Rotate actors // Rotate actors
if (TILE_TYPES[tile.name].is_actor) { if (TILE_TYPES[tile.type.name].is_actor) {
tile.direction = DIRECTIONS[tile.direction ?? 'south'].right; tile.direction = DIRECTIONS[tile.direction ?? 'south'].right;
} }
} }
@ -912,15 +912,17 @@ class Editor extends PrimaryView {
// had some kind of force floor // had some kind of force floor
if (i === 2) { if (i === 2) {
let prevcell = this.stored_level.cells[prevy][prevx]; let prevcell = this.stored_level.cells[prevy][prevx];
if (prevcell[0].name.startsWith('force_floor_')) { if (prevcell[0].type.name.startsWith('force_floor_')) {
prevcell[0].name = name; prevcell[0].type = TILE_TYPES[name];
} }
} }
// Drawing a loop with force floors creates ice (but not in the previous // Drawing a loop with force floors creates ice (but not in the previous
// cell, obviously) // cell, obviously)
let cell = this.stored_level.cells[cy][cx]; let cell = this.stored_level.cells[cy][cx];
if (cell[0].name.startsWith('force_floor_') && cell[0].name !== name) { if (cell[0].type.name.startsWith('force_floor_') &&
cell[0].type.name !== name)
{
name = 'ice'; name = 'ice';
} }
this.place_in_cell(cx, cy, name); this.place_in_cell(cx, cy, name);
@ -1083,16 +1085,16 @@ class Editor extends PrimaryView {
// combine e.g. the tent with thin walls // combine e.g. the tent with thin walls
if (type.draw_layer === 0) { if (type.draw_layer === 0) {
cell.length = 0; cell.length = 0;
cell.push({name}); cell.push({type});
} }
else { else {
for (let i = cell.length - 1; i >= 0; i--) { for (let i = cell.length - 1; i >= 0; i--) {
if (TILE_TYPES[cell[i].name].draw_layer === type.draw_layer) { if (cell[i].type.draw_layer === type.draw_layer) {
cell.splice(i, 1); cell.splice(i, 1);
} }
} }
cell.push({name}); cell.push({type});
cell.sort((a, b) => TILE_TYPES[b.name].draw_layer - TILE_TYPES[a.name].draw_layer); cell.sort((a, b) => b.type.draw_layer - a.type.draw_layer);
} }
} }
} }
@ -1144,10 +1146,10 @@ class Splash extends PrimaryView {
stored_level.size_y = 32; stored_level.size_y = 32;
for (let i = 0; i < 1024; i++) { for (let i = 0; i < 1024; i++) {
let cell = new format_util.StoredCell; let cell = new format_util.StoredCell;
cell.push({name: 'floor'}); cell.push({type: TILE_TYPES['floor']});
stored_level.linear_cells.push(cell); stored_level.linear_cells.push(cell);
} }
stored_level.linear_cells[0].push({name: 'player'}); stored_level.linear_cells[0].push({type: TILE_TYPES['player']});
let stored_game = new format_util.StoredGame; let stored_game = new format_util.StoredGame;
stored_game.levels.push(stored_level); stored_game.levels.push(stored_level);

View File

@ -94,23 +94,13 @@ export class CanvasRenderer {
for (let x = xf0; x <= x1; x++) { for (let x = xf0; x <= x1; x++) {
for (let y = yf0; y <= y1; y++) { for (let y = yf0; y <= y1; y++) {
for (let tile of this.level.cells[y][x]) { for (let tile of this.level.cells[y][x]) {
let type; if (tile.type.draw_layer !== layer)
if (tile.name) {
// FIXME editor hack
type = TILE_TYPES[tile.name];
}
else {
type = tile.type;
}
if (type.draw_layer !== layer)
continue; continue;
if (! tile.type) { if (tile.type.is_actor &&
// FIXME not a real tile, really not ideal, editor hack // FIXME kind of a hack for the editor, which uses bare tile objects
this.tileset.draw_type(tile.name, tile, this.level, ctx, x - x0, y - y0); tile.visual_position)
} {
else if (type.is_actor) {
// Handle smooth scrolling // Handle smooth scrolling
let [vx, vy] = tile.visual_position(tic_offset); let [vx, vy] = tile.visual_position(tic_offset);
// Round this to the pixel grid too! // Round this to the pixel grid too!

View File

@ -7,22 +7,13 @@ const LAYER_ACTOR = 2;
const LAYER_OVERLAY = 3; const LAYER_OVERLAY = 3;
// TODO cc2 order is: swivel, thinwalls, canopy (and yes you can have them all in the same tile) // TODO cc2 order is: swivel, thinwalls, canopy (and yes you can have them all in the same tile)
// TODO get rid of mentions of 'modifier' here, put them in the c2m loader
// TODO maybe get rid of 'load' entirely and copy everything from the tile template. also make the template ref the type instead of just having a name. in fact just make them fucking tiles? (ah but they shouldn't have state like inventory)
const TILE_TYPES = { const TILE_TYPES = {
// Floors and walls // Floors and walls
floor: { floor: {
draw_layer: LAYER_TERRAIN, draw_layer: LAYER_TERRAIN,
load(me, template) {
me.wire_directions = template.wire_directions;
me.wire_tunnel_directions = template.wire_tunnel_directions;
},
}, },
floor_letter: { floor_letter: {
draw_layer: LAYER_TERRAIN, draw_layer: LAYER_TERRAIN,
load(me, template) {
me.ascii_code = template.modifier;
},
}, },
floor_custom_green: { floor_custom_green: {
draw_layer: LAYER_TERRAIN, draw_layer: LAYER_TERRAIN,
@ -529,9 +520,6 @@ const TILE_TYPES = {
is_actor: true, is_actor: true,
is_block: true, is_block: true,
can_reveal_walls: true, can_reveal_walls: true,
load(me, template) {
me.arrows = template.directional_block_arrows;
},
ignores: new Set(['fire']), ignores: new Set(['fire']),
movement_speed: 4, movement_speed: 4,
pushes: { pushes: {
@ -587,10 +575,6 @@ const TILE_TYPES = {
draw_layer: LAYER_TERRAIN, draw_layer: LAYER_TERRAIN,
// TODO not the case for an empty one in cc2, apparently // TODO not the case for an empty one in cc2, apparently
blocks_all: true, blocks_all: true,
load(me, template) {
// FIXME not actually right, this is a bitmask
me.clone_direction = template.modifier;
},
activate(me, level) { activate(me, level) {
let cell = me.cell; let cell = me.cell;
// Copy, so we don't end up repeatedly cloning the same object // Copy, so we don't end up repeatedly cloning the same object
@ -797,10 +781,6 @@ const TILE_TYPES = {
button_pink: { button_pink: {
// TODO not implemented // TODO not implemented
draw_layer: LAYER_TERRAIN, draw_layer: LAYER_TERRAIN,
load(me, template) {
me.wire_directions = template.wire_directions;
me.wire_tunnel_directions = template.wire_tunnel_directions;
},
}, },
button_black: { button_black: {
// TODO not implemented // TODO not implemented