Implement sokoban blocks
This commit is contained in:
parent
bf8b55a9c9
commit
ada36e8d61
@ -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)) {
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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 = {
|
||||
@ -2262,7 +2286,7 @@ const SPECIAL_PALETTE_ENTRIES = {
|
||||
'railroad/curve': { name: 'railroad', tracks: 1 << 0, track_switch: null, entered_direction: 'north' },
|
||||
'railroad/switch': { name: 'railroad', tracks: 0, track_switch: 0, entered_direction: 'north' },
|
||||
'logic_gate/not': { name: 'logic_gate', direction: 'north', gate_type: 'not' },
|
||||
'logic_gate/diode': { name: 'logic_gate', direction: 'north', gate_type: 'diode' },
|
||||
'logic_gate/diode': { name: 'logic_gate', direction: 'north', gate_type: 'diode' },
|
||||
'logic_gate/and': { name: 'logic_gate', direction: 'north', gate_type: 'and' },
|
||||
'logic_gate/or': { name: 'logic_gate', direction: 'north', gate_type: 'or' },
|
||||
'logic_gate/xor': { name: 'logic_gate', direction: 'north', gate_type: 'xor' },
|
||||
@ -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];
|
||||
|
||||
@ -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',
|
||||
|
||||
122
js/tiletypes.js
122
js/tiletypes.js
@ -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,
|
||||
|
||||
BIN
tileset-lexy.png
BIN
tileset-lexy.png
Binary file not shown.
|
Before Width: | Height: | Size: 84 KiB After Width: | Height: | Size: 85 KiB |
Binary file not shown.
Loading…
Reference in New Issue
Block a user