Implement sokoban blocks

This commit is contained in:
Eevee (Evelyn Woods) 2021-03-07 00:07:18 -07:00
parent bf8b55a9c9
commit ada36e8d61
7 changed files with 231 additions and 11 deletions

View File

@ -111,6 +111,16 @@ let modifier_wire = {
}, },
}; };
let modifier_color = {
_order: ['red', 'blue', 'yellow', 'green'],
decode(tile, modifier) {
tile.color = this._order[modifier % 4];
},
encode(tile) {
return this._order.indexOf(tile.color);
},
};
let arg_direction = { let arg_direction = {
size: 1, size: 1,
decode(tile, dirbyte) { decode(tile, dirbyte) {
@ -795,6 +805,7 @@ const TILE_ENCODING = {
has_next: true, has_next: true,
}, },
// ------------------------------------------------------------------------------------------------
// LL-specific tiles // LL-specific tiles
0xd0: { 0xd0: {
name: 'electrified_floor', name: 'electrified_floor',
@ -826,11 +837,7 @@ const TILE_ENCODING = {
has_next: true, has_next: true,
extra_args: [arg_direction], extra_args: [arg_direction],
}, },
0xd7: { // 0xd7
name: 'item_lock',
has_next: true,
is_extension: true,
},
0xd8: { 0xd8: {
name: 'dash_floor', name: 'dash_floor',
is_extension: true, is_extension: true,
@ -902,6 +909,23 @@ const TILE_ENCODING = {
modifier: modifier_wire, modifier: modifier_wire,
is_extension: true, is_extension: true,
}, },
0xf1: {
name: 'sokoban_block',
has_next: true,
modifier: modifier_color,
extra_args: [arg_direction],
is_extension: true,
},
0xf2: {
name: 'sokoban_button',
modifier: modifier_color,
is_extension: true,
},
0xf3: {
name: 'sokoban_wall',
modifier: modifier_color,
is_extension: true,
},
}; };
const REVERSE_TILE_ENCODING = {}; const REVERSE_TILE_ENCODING = {};
for (let [tile_byte, spec] of Object.entries(TILE_ENCODING)) { for (let [tile_byte, spec] of Object.entries(TILE_ENCODING)) {

View File

@ -521,6 +521,8 @@ export class Level extends LevelInterface {
// If there's exactly one yellow teleporter when the level loads, it cannot be picked up // If there's exactly one yellow teleporter when the level loads, it cannot be picked up
let yellow_teleporter_count = 0; let yellow_teleporter_count = 0;
this.allow_taking_yellow_teleporters = false; this.allow_taking_yellow_teleporters = false;
// Sokoban buttons function as a group
this.sokoban_buttons_unpressed = {};
for (let y = 0; y < this.height; y++) { for (let y = 0; y < this.height; y++) {
let row = []; let row = [];
for (let x = 0; x < this.width; x++) { for (let x = 0; x < this.width; x++) {
@ -561,6 +563,10 @@ export class Level extends LevelInterface {
this.allow_taking_yellow_teleporters = true; this.allow_taking_yellow_teleporters = true;
} }
} }
else if (tile.type.name === 'sokoban_button') {
this.sokoban_buttons_unpressed[tile.color] =
(this.sokoban_buttons_unpressed[tile.color] ?? 0) + 1;
}
} }
} }
} }

View File

@ -1613,6 +1613,18 @@ const EDITOR_PALETTE = [{
'boulder', 'boulder',
'glass_block', 'glass_block',
'logic_gate/diode', 'logic_gate/diode',
'sokoban_block/red',
'sokoban_button/red',
'sokoban_wall/red',
'sokoban_block/blue',
'sokoban_button/blue',
'sokoban_wall/blue',
'sokoban_block/green',
'sokoban_button/green',
'sokoban_wall/green',
'sokoban_block/yellow',
'sokoban_button/yellow',
'sokoban_wall/yellow',
], ],
}]; }];
@ -2247,6 +2259,18 @@ const EDITOR_TILE_DESCRIPTIONS = {
name: "Glass block", name: "Glass block",
desc: "Similar to a dirt block, but stores the first item it moves over, dropping it when destroyed and cloning it in a cloning machine. Has ice block/frame block collision. Turns into floor in water. Doesn't have dirt block immunities.", desc: "Similar to a dirt block, but stores the first item it moves over, dropping it when destroyed and cloning it in a cloning machine. Has ice block/frame block collision. Turns into floor in water. Doesn't have dirt block immunities.",
}, },
sokoban_block: {
name: "Sokoban block",
desc: "Similar to a dirt block. Turns to colored floor in water. Can't pass over colored floor of a different color. Has no effect on sokoban buttons of a different color.",
},
sokoban_button: {
name: "Sokoban button",
desc: "Changes sokoban walls of the same color to floor, but only while all buttons of the same color are held. Not affected by sokoban blocks of a different color.",
},
sokoban_wall: {
name: "Sokoban wall",
desc: "Acts like wall. Turns to floor while all sokoban buttons of the same color are pressed.",
},
}; };
const SPECIAL_PALETTE_ENTRIES = { const SPECIAL_PALETTE_ENTRIES = {
@ -2271,6 +2295,18 @@ const SPECIAL_PALETTE_ENTRIES = {
'logic_gate/latch-ccw': { name: 'logic_gate', direction: 'north', gate_type: 'latch-ccw' }, 'logic_gate/latch-ccw': { name: 'logic_gate', direction: 'north', gate_type: 'latch-ccw' },
'logic_gate/counter': { name: 'logic_gate', direction: 'north', gate_type: 'counter', memory: 0 }, 'logic_gate/counter': { name: 'logic_gate', direction: 'north', gate_type: 'counter', memory: 0 },
'circuit_block/xxx': { name: 'circuit_block', direction: 'south', wire_directions: 0xf }, 'circuit_block/xxx': { name: 'circuit_block', direction: 'south', wire_directions: 0xf },
'sokoban_block/red': { name: 'sokoban_block', color: 'red' },
'sokoban_button/red': { name: 'sokoban_button', color: 'red' },
'sokoban_wall/red': { name: 'sokoban_wall', color: 'red' },
'sokoban_block/blue': { name: 'sokoban_block', color: 'blue' },
'sokoban_button/blue': { name: 'sokoban_button', color: 'blue' },
'sokoban_wall/blue': { name: 'sokoban_wall', color: 'blue' },
'sokoban_block/yellow': { name: 'sokoban_block', color: 'yellow' },
'sokoban_button/yellow':{ name: 'sokoban_button', color: 'yellow' },
'sokoban_wall/yellow': { name: 'sokoban_wall', color: 'yellow' },
'sokoban_block/green': { name: 'sokoban_block', color: 'green' },
'sokoban_button/green': { name: 'sokoban_button', color: 'green' },
'sokoban_wall/green': { name: 'sokoban_wall', color: 'green' },
}; };
const _RAILROAD_ROTATED_LEFT = [3, 0, 1, 2, 5, 4]; const _RAILROAD_ROTATED_LEFT = [3, 0, 1, 2, 5, 4];
const _RAILROAD_ROTATED_RIGHT = [1, 2, 3, 0, 5, 4]; const _RAILROAD_ROTATED_RIGHT = [1, 2, 3, 0, 5, 4];

View File

@ -1324,15 +1324,15 @@ export const LL_TILESET_LAYOUT = {
duration: 20, duration: 20,
all: [[8, 17], [9, 17], [10, 17], [9, 17]], all: [[8, 17], [9, 17], [10, 17], [9, 17]],
}, },
bowling_ball: [11, 16], bowling_ball: [9, 19],
rolling_ball: { rolling_ball: {
__special__: 'animated', __special__: 'animated',
global: false, global: false,
duration: 1, duration: 1,
north: [[12, 16], [13, 16], [11, 17], [11, 17], [11, 17], [14, 16], [15, 16], [11, 16]], north: [[14, 16], [15, 16], [13, 17], [13, 17], [13, 17], [11, 16], [12, 16], [13, 16]],
east: [[12, 17], [13, 17], [11, 17], [11, 17], [11, 17], [14, 17], [15, 17], [11, 16]], east: [[11, 17], [12, 17], [13, 17], [13, 17], [13, 17], [14, 17], [15, 17], [13, 16]],
south: [[15, 16], [14, 16], [11, 17], [11, 17], [11, 17], [13, 16], [12, 16], [11, 16]], south: [[12, 16], [11, 16], [13, 17], [13, 17], [13, 17], [15, 16], [14, 16], [13, 16]],
west: [[15, 17], [14, 17], [11, 17], [11, 17], [11, 17], [13, 17], [12, 17], [11, 16]], west: [[15, 17], [14, 17], [13, 17], [13, 17], [13, 17], [12, 17], [11, 17], [13, 16]],
}, },
// LL bombs aren't animated // LL bombs aren't animated
bomb: [11, 18], bomb: [11, 18],
@ -1869,6 +1869,38 @@ export const LL_TILESET_LAYOUT = {
wired: [17, 22], wired: [17, 22],
wired_cross: [18, 22], wired_cross: [18, 22],
}, },
sokoban_block: {
__special__: 'visual-state',
red: [26, 20],
blue: [26, 21],
yellow: [26, 22],
green: [26, 23],
},
sokoban_button: {
__special__: 'visual-state',
red_released: [28, 20],
blue_released: [28, 21],
yellow_released: [28, 22],
green_released: [28, 23],
red_pressed: [29, 20],
blue_pressed: [29, 21],
yellow_pressed: [29, 22],
green_pressed: [29, 23],
},
sokoban_wall: {
__special__: 'visual-state',
red: [30, 20],
blue: [30, 21],
yellow: [30, 22],
green: [30, 23],
},
sokoban_floor: {
__special__: 'visual-state',
red: [31, 20],
blue: [31, 21],
yellow: [31, 22],
green: [31, 23],
},
rover: { rover: {
__special__: 'rover', __special__: 'rover',

View File

@ -272,18 +272,30 @@ const TILE_TYPES = {
floor_custom_green: { floor_custom_green: {
layer: LAYERS.terrain, layer: LAYERS.terrain,
blocks_collision: COLLISION.ghost, blocks_collision: COLLISION.ghost,
blocks(me, level, other) {
return (other.type.name === 'sokoban_block' && other.color !== 'green');
},
}, },
floor_custom_pink: { floor_custom_pink: {
layer: LAYERS.terrain, layer: LAYERS.terrain,
blocks_collision: COLLISION.ghost, blocks_collision: COLLISION.ghost,
blocks(me, level, other) {
return (other.type.name === 'sokoban_block' && other.color !== 'red');
},
}, },
floor_custom_yellow: { floor_custom_yellow: {
layer: LAYERS.terrain, layer: LAYERS.terrain,
blocks_collision: COLLISION.ghost, blocks_collision: COLLISION.ghost,
blocks(me, level, other) {
return (other.type.name === 'sokoban_block' && other.color !== 'yellow');
},
}, },
floor_custom_blue: { floor_custom_blue: {
layer: LAYERS.terrain, layer: LAYERS.terrain,
blocks_collision: COLLISION.ghost, blocks_collision: COLLISION.ghost,
blocks(me, level, other) {
return (other.type.name === 'sokoban_block' && other.color !== 'blue');
},
}, },
wall: { wall: {
layer: LAYERS.terrain, layer: LAYERS.terrain,
@ -821,6 +833,15 @@ const TILE_TYPES = {
level.transmute_tile(other, 'splash'); level.transmute_tile(other, 'splash');
level.recalculate_circuitry_next_wire_phase = true; level.recalculate_circuitry_next_wire_phase = true;
} }
else if (other.type.name === 'sokoban_block') {
level.transmute_tile(me, ({
red: 'floor_custom_pink',
blue: 'floor_custom_blue',
yellow: 'floor_custom_yellow',
green: 'floor_custom_green',
})[other.color]);
level.transmute_tile(other, 'splash');
}
else if (other.type.is_real_player) { else if (other.type.is_real_player) {
level.fail('drowned', me, other); level.fail('drowned', me, other);
} }
@ -1243,6 +1264,8 @@ const TILE_TYPES = {
ice_block: true, ice_block: true,
frame_block: true, frame_block: true,
boulder: true, boulder: true,
glass_block: true,
sokoban_block: true,
}, },
on_after_bumped(me, level, other) { on_after_bumped(me, level, other) {
// Fireballs melt ice blocks on regular floor FIXME and water! // Fireballs melt ice blocks on regular floor FIXME and water!
@ -1276,6 +1299,7 @@ const TILE_TYPES = {
frame_block: true, frame_block: true,
boulder: true, boulder: true,
glass_block: true, glass_block: true,
sokoban_block: true,
}, },
on_clone(me, original) { on_clone(me, original) {
me.arrows = new Set(original.arrows); me.arrows = new Set(original.arrows);
@ -1446,6 +1470,90 @@ const TILE_TYPES = {
}, },
}, },
// Sokoban blocks, buttons, and walls -- they each come in four colors, the buttons can be
// pressed by anything EXCEPT a sokoban block of the WRONG color, and the walls become floors
// only when ALL the buttons of the corresponding color are pressed
sokoban_block: {
layer: LAYERS.actor,
collision_mask: COLLISION.block_cc1,
blocks_collision: COLLISION.all,
item_pickup_priority: PICKUP_PRIORITIES.always,
is_actor: true,
is_block: true,
can_reverse_on_railroad: true,
movement_speed: 4,
populate_defaults(me) {
me.color = 'red';
},
visual_state(me) {
return me.color ?? 'red';
},
},
sokoban_button: {
layer: LAYERS.terrain,
populate_defaults(me) {
me.color = 'red';
},
on_arrive(me, level, other) {
if (other.type.name === 'sokoban_block' && me.color !== other.color)
return;
level.sfx.play_once('button-press', me.cell);
level.sokoban_buttons_unpressed[me.color] -= 1;
level._push_pending_undo(() => {
level.sokoban_buttons_unpressed[me.color] += 1;
});
if (level.sokoban_buttons_unpressed[me.color] === 0) {
for (let cell of level.linear_cells) {
let terrain = cell.get_terrain();
if (terrain.type.name === 'sokoban_wall' && terrain.color === me.color) {
level.transmute_tile(terrain, 'sokoban_floor');
}
}
}
},
on_depart(me, level, other) {
if (other.type.name === 'sokoban_block' && me.color !== other.color)
return;
level.sfx.play_once('button-release', me.cell);
level.sokoban_buttons_unpressed[me.color] += 1;
level._push_pending_undo(() => {
level.sokoban_buttons_unpressed[me.color] -= 1;
});
if (level.sokoban_buttons_unpressed[me.color] === 1) {
for (let cell of level.linear_cells) {
let terrain = cell.get_terrain();
if (terrain.type.name === 'sokoban_floor' && terrain.color === me.color) {
level.transmute_tile(terrain, 'sokoban_wall');
}
}
}
},
visual_state(me) {
return (me.color ?? 'red') + '_' + button_visual_state(me);
},
},
sokoban_wall: {
layer: LAYERS.terrain,
blocks_collision: COLLISION.all_but_ghost,
populate_defaults(me) {
me.color = 'red';
},
visual_state(me) {
return me.color ?? 'red';
},
},
sokoban_floor: {
layer: LAYERS.terrain,
populate_defaults(me) {
me.color = 'red';
},
visual_state(me) {
return me.color ?? 'red';
},
},
// ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------
// Floor mechanisms // Floor mechanisms
cloner: { cloner: {
@ -1613,6 +1721,14 @@ const TILE_TYPES = {
let options = me.type._blob_mogrifications; let options = me.type._blob_mogrifications;
level.transmute_tile(other, options[level.prng() % options.length]); level.transmute_tile(other, options[level.prng() % options.length]);
} }
else if (name === 'sokoban_block') {
level._set_tile_prop(other, 'color', ({
red: 'blue',
blue: 'red',
yellow: 'green',
green: 'yellow',
})[other.color]);
}
else { else {
return; return;
} }
@ -2408,6 +2524,7 @@ const TILE_TYPES = {
circuit_block: true, circuit_block: true,
boulder: true, boulder: true,
glass_block: true, glass_block: true,
sokoban_block: true,
}, },
decide_movement(me, level) { decide_movement(me, level) {
if (me.pending_decision) { if (me.pending_decision) {
@ -2531,6 +2648,7 @@ const TILE_TYPES = {
circuit_block: true, circuit_block: true,
boulder: true, boulder: true,
glass_block: true, glass_block: true,
sokoban_block: true,
}, },
on_ready(me, level) { on_ready(me, level) {
me.current_emulatee = 0; me.current_emulatee = 0;
@ -2864,6 +2982,7 @@ const TILE_TYPES = {
circuit_block: true, circuit_block: true,
boulder: true, boulder: true,
glass_block: true, glass_block: true,
sokoban_block: true,
}, },
infinite_items: { infinite_items: {
key_green: true, key_green: true,
@ -2888,6 +3007,7 @@ const TILE_TYPES = {
circuit_block: true, circuit_block: true,
boulder: true, boulder: true,
glass_block: true, glass_block: true,
sokoban_block: true,
}, },
infinite_items: { infinite_items: {
key_yellow: true, key_yellow: true,
@ -2911,6 +3031,7 @@ const TILE_TYPES = {
circuit_block: true, circuit_block: true,
boulder: true, boulder: true,
glass_block: true, glass_block: true,
sokoban_block: true,
}, },
infinite_items: { infinite_items: {
key_green: true, key_green: true,
@ -2938,6 +3059,7 @@ const TILE_TYPES = {
circuit_block: true, circuit_block: true,
boulder: true, boulder: true,
glass_block: true, glass_block: true,
sokoban_block: true,
}, },
infinite_items: { infinite_items: {
key_yellow: true, key_yellow: true,

Binary file not shown.

Before

Width:  |  Height:  |  Size: 84 KiB

After

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.