Implement implicit terrain for C2M; implement thin walls

This commit is contained in:
Eevee (Evelyn Woods) 2020-08-29 01:14:59 -06:00
parent a9b1af8e62
commit ec872d93d2
4 changed files with 111 additions and 97 deletions

View File

@ -24,28 +24,28 @@ const TILE_ENCODING = {
//0x13: 'teleport_green', //0x13: 'teleport_green',
0x14: 'exit', 0x14: 'exit',
//0x15: 'slime', //0x15: 'slime',
0x16: ['player', 'direction', 'next'], 0x16: ['player', '#direction', '#next'],
0x17: ['dirt_block', 'direction', 'next'], 0x17: ['dirt_block', '#direction', '#next'],
0x18: ['walker', 'direction', 'next'], 0x18: ['walker', '#direction', '#next'],
0x19: ['glider', 'direction', 'next'], 0x19: ['glider', '#direction', '#next'],
0x1a: ['ice_block', 'direction', 'next'], 0x1a: ['ice_block', '#direction', '#next'],
0x1b: ['thinwall_e', 'next'], 0x1b: ['thinwall_e', '#next'],
0x1c: ['thinwall_s', 'next'], 0x1c: ['thinwall_s', '#next'],
0x1d: ['thinwall_se', 'next'], 0x1d: ['thinwall_se', '#next'],
0x1e: 'gravel', 0x1e: 'gravel',
0x1f: 'button_green', 0x1f: 'button_green',
0x20: 'button_blue', 0x20: 'button_blue',
0x21: ['tank_blue', 'direction', 'next'], 0x21: ['tank_blue', '#direction', '#next'],
0x22: 'door_red', 0x22: 'door_red',
0x23: 'door_blue', 0x23: 'door_blue',
0x24: 'door_yellow', 0x24: 'door_yellow',
0x25: 'door_green', 0x25: 'door_green',
0x26: ['key_red', 'next'], 0x26: ['key_red', '#next'],
0x27: ['key_blue', 'next'], 0x27: ['key_blue', '#next'],
0x28: ['key_yellow', 'next'], 0x28: ['key_yellow', '#next'],
0x29: ['key_green', 'next'], 0x29: ['key_green', '#next'],
0x2a: ['chip', 'next'], 0x2a: ['chip', '#next'],
0x2b: ['chip_extra', 'next'], 0x2b: ['chip_extra', '#next'],
0x2c: 'socket', 0x2c: 'socket',
0x2d: 'popwall', 0x2d: 'popwall',
0x2e: 'wall_appearing', 0x2e: 'wall_appearing',
@ -53,20 +53,20 @@ const TILE_ENCODING = {
0x30: 'fake_wall', 0x30: 'fake_wall',
0x31: 'fake_floor', 0x31: 'fake_floor',
0x32: 'dirt', 0x32: 'dirt',
0x33: ['bug', 'direction', 'next'], 0x33: ['bug', '#direction', '#next'],
0x34: ['paramecium', 'direction', 'next'], 0x34: ['paramecium', '#direction', '#next'],
0x35: ['ball', 'direction', 'next'], 0x35: ['ball', '#direction', '#next'],
0x36: ['blob', 'direction', 'next'], 0x36: ['blob', '#direction', '#next'],
0x37: ['teeth', 'direction', 'next'], 0x37: ['teeth', '#direction', '#next'],
0x38: ['fireball', 'direction', 'next'], 0x38: ['fireball', '#direction', '#next'],
0x39: 'button_red', 0x39: 'button_red',
0x3a: 'button_brown', 0x3a: 'button_brown',
0x3b: ['cleats', 'next'], 0x3b: ['cleats', '#next'],
0x3c: ['suction_boots', 'next'], 0x3c: ['suction_boots', '#next'],
0x3d: ['fire_boots', 'next'], 0x3d: ['fire_boots', '#next'],
0x3e: ['flippers', 'next'], 0x3e: ['flippers', '#next'],
0x3f: 'thief_keys', 0x3f: 'thief_keys',
0x40: ['bomb', 'next'], 0x40: ['bomb', '#next'],
//0x41: Open trap (unused in main levels) : //0x41: Open trap (unused in main levels) :
0x42: 'trap', 0x42: 'trap',
0x43: 'cloner', 0x43: 'cloner',
@ -74,46 +74,46 @@ const TILE_ENCODING = {
0x45: 'hint', 0x45: 'hint',
//0x46: 'force_floor_all', //0x46: 'force_floor_all',
// 0x47: 'button_gray', // 0x47: 'button_gray',
0x48: 'swivel_sw', 0x48: ['swivel_sw', 'swivel_floor'],
0x49: 'swivel_nw', 0x49: ['swivel_nw', 'swivel_floor'],
0x4a: 'swivel_ne', 0x4a: ['swivel_ne', 'swivel_floor'],
0x4b: 'swivel_se', 0x4b: ['swivel_se', 'swivel_floor'],
// 0x4c: Time bonus : 'next' // 0x4c: Time bonus : '#next'
// 0x4d: Stopwatch : 'next' // 0x4d: Stopwatch : '#next'
// 0x4e: Transmogrifier : // 0x4e: Transmogrifier :
// 0x4f: Railroad track (Modifier required, see section below) : // 0x4f: Railroad track (Modifier required, see section below) :
// 0x50: Steel wall : // 0x50: Steel wall :
// 0x51: Time bomb : 'next' // 0x51: Time bomb : '#next'
// 0x52: Helmet : 'next' // 0x52: Helmet : '#next'
// 0x53: (Unused) : 'direction', 'next' // 0x53: (Unused) : '#direction', '#next'
// 0x54: (Unused) : // 0x54: (Unused) :
// 0x55: (Unused) : // 0x55: (Unused) :
// 0x56: Melinda : 'direction', 'next' // 0x56: Melinda : '#direction', '#next'
// 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: Hiking boots : '#next'
// 0x5a: Male-only sign : // 0x5a: Male-only sign :
// 0x5b: Female-only sign : // 0x5b: Female-only sign :
// 0x5c: Inverter gate (N) : Modifier allows other gates, see below // 0x5c: Inverter gate (N) : Modifier allows other gates, see below
// 0x5d: (Unused) : 'direction', 'next' // 0x5d: (Unused) : '#direction', '#next'
// 0x5e: Logic switch (ON) : // 0x5e: Logic switch (ON) :
// 0x5f: Flame jet (OFF) : // 0x5f: Flame jet (OFF) :
// 0x60: Flame jet (ON) : // 0x60: Flame jet (ON) :
// 0x61: Orange button : // 0x61: Orange button :
// 0x62: Lightning bolt : 'next' // 0x62: Lightning bolt : '#next'
// 0x63: Yellow tank : 'direction', 'next' // 0x63: Yellow tank : '#direction', '#next'
// 0x64: Yellow tank button : // 0x64: Yellow tank button :
// 0x65: Mirror Chip : 'direction', 'next' // 0x65: Mirror Chip : '#direction', '#next'
// 0x66: Mirror Melinda : 'direction', 'next' // 0x66: Mirror Melinda : '#direction', '#next'
// 0x67: (Unused) : // 0x67: (Unused) :
// 0x68: Bowling ball : 'next' // 0x68: Bowling ball : '#next'
// 0x69: Rover : 'direction', 'next' // 0x69: Rover : '#direction', '#next'
// 0x6a: Time penalty : 'next' // 0x6a: Time penalty : '#next'
// 0x6b: Custom floor (green) : Modifier allows other styles, see below // 0x6b: Custom floor (green) : Modifier allows other styles, see below
// 0x6c: (Unused) : // 0x6c: (Unused) :
// 0x6d: Thin wall / Canopy : Panel/Canopy bitmask (see below), 'next' // 0x6d: Thin wall / Canopy : Panel/Canopy bitmask (see below), '#next'
// 0x6e: (Unused) : // 0x6e: (Unused) :
// 0x6f: Railroad sign : 'next' // 0x6f: Railroad sign : '#next'
// 0x70: Custom wall (green) : Modifier allows other styles, see below // 0x70: Custom wall (green) : Modifier allows other styles, see below
// TODO needs a preceding modifier but that's not done yet (and should enforce that a modifier is followed by a modifiable tile?) // TODO needs a preceding modifier but that's not done yet (and should enforce that a modifier is followed by a modifiable tile?)
0x71: 'floor_letter', 0x71: 'floor_letter',
@ -124,32 +124,32 @@ const TILE_ENCODING = {
// 0x76: 8-bit Modifier (see Modifier section below) : 1 modifier byte, Tile Specification for affected tile // 0x76: 8-bit Modifier (see Modifier section below) : 1 modifier byte, Tile Specification for affected tile
// 0x77: 16-bit Modifier (see Modifier section below) : 2 modifier bytes, Tile Specification for affected tile // 0x77: 16-bit Modifier (see Modifier section below) : 2 modifier bytes, Tile Specification for affected tile
// 0x78: 32-bit Modifier (see Modifier section below) : 4 modifier bytes, Tile Specification for affected tile // 0x78: 32-bit Modifier (see Modifier section below) : 4 modifier bytes, Tile Specification for affected tile
// 0x79: (Unused) : 'direction', 'next' // 0x79: (Unused) : '#direction', '#next'
0x7a: ['score_10', 'next'], 0x7a: ['score_10', '#next'],
0x7b: ['score_100', 'next'], 0x7b: ['score_100', '#next'],
0x7c: ['score_1000', 'next'], 0x7c: ['score_1000', '#next'],
// 0x7d: Solid green wall : // 0x7d: Solid green wall :
// 0x7e: False green wall : // 0x7e: False green wall :
0x7f: ['forbidden', 'next'], 0x7f: ['forbidden', '#next'],
0x80: ['score_2x', 'next'], 0x80: ['score_2x', '#next'],
// 0x81: Directional block : 'direction', Directional Arrows Bitmask, 'next' // 0x81: Directional block : '#direction', Directional Arrows Bitmask, '#next'
// 0x82: Floor mimic : 'direction', 'next' // 0x82: Floor mimic : '#direction', '#next'
// 0x83: Green bomb : 'next' // 0x83: Green bomb : '#next'
// 0x84: Green chip : 'next' // 0x84: Green chip : '#next'
// 0x85: (Unused) : 'next' // 0x85: (Unused) : '#next'
// 0x86: (Unused) : 'next' // 0x86: (Unused) : '#next'
// 0x87: Black button : // 0x87: Black button :
// 0x88: ON/OFF switch (OFF) : // 0x88: ON/OFF switch (OFF) :
// 0x89: ON/OFF switch (ON) : // 0x89: ON/OFF switch (ON) :
0x8a: 'thief_tools', 0x8a: 'thief_tools',
// 0x8b: Ghost : 'direction', 'next' // 0x8b: Ghost : '#direction', '#next'
// 0x8c: Steel foil : 'next' // 0x8c: Steel foil : '#next'
0x8d: 'turtle', 0x8d: ['turtle', 'water'],
// 0x8e: Secret eye : 'next' // 0x8e: Secret eye : '#next'
// 0x8f: Thief bribe : 'next' // 0x8f: Thief bribe : '#next'
// 0x90: Speed boots : 'next' // 0x90: Speed boots : '#next'
// 0x91: (Unused) : // 0x91: (Unused) :
// 0x92: Hook : 'next' // 0x92: Hook : '#next'
}; };
// 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
@ -354,7 +354,7 @@ export function parse_level(buf) {
// Handle extra arguments // Handle extra arguments
let has_next = false; let has_next = false;
for (let arg of args) { for (let arg of args) {
if (arg === 'direction') { if (arg === '#direction') {
let dirbyte = bytes[p]; let dirbyte = bytes[p];
p++; p++;
let direction = ['north', 'east', 'south', 'west'][dirbyte]; let direction = ['north', 'east', 'south', 'west'][dirbyte];
@ -364,9 +364,14 @@ export function parse_level(buf) {
} }
tile.direction = direction; tile.direction = direction;
} }
else if (arg === 'next') { else if (arg === '#next') {
has_next = true; has_next = true;
} }
else {
// Anything else is an implicit next tile, e.g.
// turtles imply water underneath
cell.push({name: arg});
}
} }
if (! has_next) if (! has_next)

View File

@ -367,9 +367,28 @@ class Level {
let blocked; let blocked;
if (goal_x >= 0 && goal_x < this.width && goal_y >= 0 && goal_y < this.height) { if (goal_x >= 0 && goal_x < this.width && goal_y >= 0 && goal_y < this.height) {
let goal_cell = this.cells[goal_y][goal_x]; // Check for a thin wall in our current cell first
goal_cell.each(tile => { let original_cell = this.cells[actor.y][actor.x];
if (tile !== actor && tile.type.blocks) { original_cell.each(tile => {
if (tile !== actor && tile.type.thin_walls &&
tile.type.thin_walls.has(direction))
{
blocked = true;
return;
}
});
// Only bother touching the goal cell if we're not already trapped in this one
// FIXME actually, this prevents flicking!
if (! blocked) {
let goal_cell = this.cells[goal_y][goal_x];
goal_cell.each(tile => {
if (! (
tile.type.blocks ||
(tile.type.thin_walls && tile.type.thin_walls.has(DIRECTIONS[direction].opposite))
))
return;
if (actor.type.pushes && actor.type.pushes[tile.type.name]) { if (actor.type.pushes && actor.type.pushes[tile.type.name]) {
if (this.attempt_step(tile, direction)) if (this.attempt_step(tile, direction))
// It moved out of the way! // It moved out of the way!
@ -383,8 +402,8 @@ class Level {
} }
blocked = true; blocked = true;
// XXX should i break here, or bump everything? // XXX should i break here, or bump everything?
} });
}); }
} }
else { else {
// Hit the edge // Hit the edge

View File

@ -30,8 +30,9 @@ export const CC2_TILESET_LAYOUT = {
// TODO these guys don't have floor underneath. // TODO these guys don't have floor underneath.
swivel_sw: [9, 11], swivel_sw: [9, 11],
swivel_nw: [10, 11], swivel_nw: [10, 11],
swivel_ne: [12, 11], swivel_ne: [11, 11],
swivel_se: [13, 11], swivel_se: [12, 11],
swivel_floor: [13, 11],
forbidden: [14, 5], forbidden: [14, 5],
turtle: [13, 12], // TODO also 14 + 15 for sinking turtle: [13, 12], // TODO also 14 + 15 for sinking
popwall: [8, 10], popwall: [8, 10],

View File

@ -47,17 +47,18 @@ const TILE_TYPES = {
}, },
// Swivel doors // Swivel doors
swivel_floor: {},
swivel_ne: { swivel_ne: {
thin_walls: new Set(['north'], ['east']), thin_walls: new Set(['north', 'east']),
}, },
swivel_se: { swivel_se: {
thin_walls: new Set(['south'], ['east']), thin_walls: new Set(['south', 'east']),
}, },
swivel_sw: { swivel_sw: {
thin_walls: new Set(['south'], ['west']), thin_walls: new Set(['south', 'west']),
}, },
swivel_nw: { swivel_nw: {
thin_walls: new Set(['north'], ['west']), thin_walls: new Set(['north', 'west']),
}, },
// Locked doors // Locked doors
@ -140,10 +141,7 @@ const TILE_TYPES = {
} }
}, },
ice_sw: { ice_sw: {
thin_walls: { thin_walls: new Set(['south', 'west']),
south: true,
west: true,
},
on_arrive(me, level, other) { on_arrive(me, level, other) {
if (other.direction === 'south') { if (other.direction === 'south') {
other.direction = 'east'; other.direction = 'east';
@ -155,10 +153,7 @@ const TILE_TYPES = {
} }
}, },
ice_nw: { ice_nw: {
thin_walls: { thin_walls: new Set(['north', 'west']),
north: true,
west: true,
},
on_arrive(me, level, other) { on_arrive(me, level, other) {
if (other.direction === 'north') { if (other.direction === 'north') {
other.direction = 'east'; other.direction = 'east';
@ -170,10 +165,7 @@ const TILE_TYPES = {
} }
}, },
ice_ne: { ice_ne: {
thin_walls: { thin_walls: new Set(['north', 'east']),
north: true,
east: true,
},
on_arrive(me, level, other) { on_arrive(me, level, other) {
if (other.direction === 'north') { if (other.direction === 'north') {
other.direction = 'west'; other.direction = 'west';
@ -185,10 +177,7 @@ const TILE_TYPES = {
} }
}, },
ice_se: { ice_se: {
thin_walls: { thin_walls: new Set(['south', 'east']),
south: true,
east: true,
},
on_arrive(me, level, other) { on_arrive(me, level, other) {
if (other.direction === 'south') { if (other.direction === 'south') {
other.direction = 'west'; other.direction = 'west';