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:
parent
325960b609
commit
cce28c2d7e
794
js/format-c2m.js
794
js/format-c2m.js
@ -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 = {
|
||||||
tile.wire_directions = modifier & 0x0f;
|
decode(tile, modifier) {
|
||||||
tile.wire_tunnel_directions = (modifier & 0xf0) >> 4;
|
tile.wire_directions = modifier & 0x0f;
|
||||||
}
|
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;
|
return spec;
|
||||||
let args = [];
|
|
||||||
if (spec instanceof Array) {
|
|
||||||
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 ', '');
|
||||||
|
}
|
||||||
|
|||||||
12
js/game.js
12
js/game.js
@ -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!");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
26
js/main.js
26
js/main.js
@ -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);
|
||||||
|
|||||||
@ -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!
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user