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 = {
size: 1,
decode(tile, dirbyte) {
@ -795,6 +805,7 @@ const TILE_ENCODING = {
has_next: true,
},
// ------------------------------------------------------------------------------------------------
// LL-specific tiles
0xd0: {
name: 'electrified_floor',
@ -826,11 +837,7 @@ const TILE_ENCODING = {
has_next: true,
extra_args: [arg_direction],
},
0xd7: {
name: 'item_lock',
has_next: true,
is_extension: true,
},
// 0xd7
0xd8: {
name: 'dash_floor',
is_extension: true,
@ -902,6 +909,23 @@ const TILE_ENCODING = {
modifier: modifier_wire,
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 = {};
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
let yellow_teleporter_count = 0;
this.allow_taking_yellow_teleporters = false;
// Sokoban buttons function as a group
this.sokoban_buttons_unpressed = {};
for (let y = 0; y < this.height; y++) {
let row = [];
for (let x = 0; x < this.width; x++) {
@ -561,6 +563,10 @@ export class Level extends LevelInterface {
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',
'glass_block',
'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",
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 = {
@ -2271,6 +2295,18 @@ const SPECIAL_PALETTE_ENTRIES = {
'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 },
'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_RIGHT = [1, 2, 3, 0, 5, 4];

View File

@ -1324,15 +1324,15 @@ export const LL_TILESET_LAYOUT = {
duration: 20,
all: [[8, 17], [9, 17], [10, 17], [9, 17]],
},
bowling_ball: [11, 16],
bowling_ball: [9, 19],
rolling_ball: {
__special__: 'animated',
global: false,
duration: 1,
north: [[12, 16], [13, 16], [11, 17], [11, 17], [11, 17], [14, 16], [15, 16], [11, 16]],
east: [[12, 17], [13, 17], [11, 17], [11, 17], [11, 17], [14, 17], [15, 17], [11, 16]],
south: [[15, 16], [14, 16], [11, 17], [11, 17], [11, 17], [13, 16], [12, 16], [11, 16]],
west: [[15, 17], [14, 17], [11, 17], [11, 17], [11, 17], [13, 17], [12, 17], [11, 16]],
north: [[14, 16], [15, 16], [13, 17], [13, 17], [13, 17], [11, 16], [12, 16], [13, 16]],
east: [[11, 17], [12, 17], [13, 17], [13, 17], [13, 17], [14, 17], [15, 17], [13, 16]],
south: [[12, 16], [11, 16], [13, 17], [13, 17], [13, 17], [15, 16], [14, 16], [13, 16]],
west: [[15, 17], [14, 17], [13, 17], [13, 17], [13, 17], [12, 17], [11, 17], [13, 16]],
},
// LL bombs aren't animated
bomb: [11, 18],
@ -1869,6 +1869,38 @@ export const LL_TILESET_LAYOUT = {
wired: [17, 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: {
__special__: 'rover',

View File

@ -272,18 +272,30 @@ const TILE_TYPES = {
floor_custom_green: {
layer: LAYERS.terrain,
blocks_collision: COLLISION.ghost,
blocks(me, level, other) {
return (other.type.name === 'sokoban_block' && other.color !== 'green');
},
},
floor_custom_pink: {
layer: LAYERS.terrain,
blocks_collision: COLLISION.ghost,
blocks(me, level, other) {
return (other.type.name === 'sokoban_block' && other.color !== 'red');
},
},
floor_custom_yellow: {
layer: LAYERS.terrain,
blocks_collision: COLLISION.ghost,
blocks(me, level, other) {
return (other.type.name === 'sokoban_block' && other.color !== 'yellow');
},
},
floor_custom_blue: {
layer: LAYERS.terrain,
blocks_collision: COLLISION.ghost,
blocks(me, level, other) {
return (other.type.name === 'sokoban_block' && other.color !== 'blue');
},
},
wall: {
layer: LAYERS.terrain,
@ -821,6 +833,15 @@ const TILE_TYPES = {
level.transmute_tile(other, 'splash');
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) {
level.fail('drowned', me, other);
}
@ -1243,6 +1264,8 @@ const TILE_TYPES = {
ice_block: true,
frame_block: true,
boulder: true,
glass_block: true,
sokoban_block: true,
},
on_after_bumped(me, level, other) {
// Fireballs melt ice blocks on regular floor FIXME and water!
@ -1276,6 +1299,7 @@ const TILE_TYPES = {
frame_block: true,
boulder: true,
glass_block: true,
sokoban_block: true,
},
on_clone(me, original) {
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
cloner: {
@ -1613,6 +1721,14 @@ const TILE_TYPES = {
let options = me.type._blob_mogrifications;
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 {
return;
}
@ -2408,6 +2524,7 @@ const TILE_TYPES = {
circuit_block: true,
boulder: true,
glass_block: true,
sokoban_block: true,
},
decide_movement(me, level) {
if (me.pending_decision) {
@ -2531,6 +2648,7 @@ const TILE_TYPES = {
circuit_block: true,
boulder: true,
glass_block: true,
sokoban_block: true,
},
on_ready(me, level) {
me.current_emulatee = 0;
@ -2864,6 +2982,7 @@ const TILE_TYPES = {
circuit_block: true,
boulder: true,
glass_block: true,
sokoban_block: true,
},
infinite_items: {
key_green: true,
@ -2888,6 +3007,7 @@ const TILE_TYPES = {
circuit_block: true,
boulder: true,
glass_block: true,
sokoban_block: true,
},
infinite_items: {
key_yellow: true,
@ -2911,6 +3031,7 @@ const TILE_TYPES = {
circuit_block: true,
boulder: true,
glass_block: true,
sokoban_block: true,
},
infinite_items: {
key_green: true,
@ -2938,6 +3059,7 @@ const TILE_TYPES = {
circuit_block: true,
boulder: true,
glass_block: true,
sokoban_block: true,
},
infinite_items: {
key_yellow: true,

Binary file not shown.

Before

Width:  |  Height:  |  Size: 84 KiB

After

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.