diff --git a/js/game.js b/js/game.js index ab60040..ebfc486 100644 --- a/js/game.js +++ b/js/game.js @@ -291,22 +291,25 @@ export class Level { let cell = connectable.cell; let x = cell.x; let y = cell.y; - let goal = connectable.type.connects_to; + // FIXME this is a single string for red/brown buttons (to match iter_tiles_in_RO) but a + // set for orange buttons (because flame jet states are separate tiles), which sucks ass + let goals = connectable.type.connects_to; // Check for custom wiring, for MSCC .DAT levels + // TODO would be neat if this applied to orange buttons too if (this.stored_level.has_custom_connections) { let n = this.stored_level.coords_to_scalar(x, y); let target_cell_n = null; - if (goal === 'trap') { + if (connectable.type.name === 'button_brown') { target_cell_n = this.stored_level.custom_trap_wiring[n] ?? null; } - else if (goal === 'cloner') { + else if (connectable.type.name === 'button_red') { target_cell_n = this.stored_level.custom_cloner_wiring[n] ?? null; } if (target_cell_n && target_cell_n < this.width * this.height) { let [tx, ty] = this.stored_level.scalar_to_coords(target_cell_n); for (let tile of this.cells[ty][tx]) { - if (tile.type.name === goal) { + if (goals === tile.type.name) { connectable.connection = tile; break; } @@ -315,8 +318,26 @@ export class Level { continue; } + // Orange buttons do a really weird diamond search + if (connectable.type.connect_order === 'diamond') { + for (let cell of this.iter_cells_in_diamond(connectable.cell)) { + let target = null; + for (let tile of cell) { + if (goals.has(tile.type.name)) { + target = tile; + break; + } + } + if (target !== null) { + connectable.connection = target; + break; + } + } + continue; + } + // Otherwise, look in reading order - for (let tile of this.iter_tiles_in_reading_order(cell, goal)) { + for (let tile of this.iter_tiles_in_reading_order(cell, goals)) { // TODO ideally this should be a weak connection somehow, since dynamite can destroy // empty cloners and probably traps too connectable.connection = tile; @@ -1189,6 +1210,26 @@ export class Level { } } + // Iterates over the grid in a diamond pattern, spreading out from the given start cell (but not + // including it). Only used for connecting orange buttons. + *iter_cells_in_diamond(start_cell) { + let max_search_radius = Math.max(this.size_x, this.size_y); + for (let dist = 1; dist <= max_search_radius; dist++) { + // Start east and move counterclockwise + let sx = start_cell.x + dist; + let sy = start_cell.y; + for (let direction of [[-1, -1], [-1, 1], [1, 1], [1, -1]]) { + for (let i = 0; i < dist; i++) { + if (this.is_point_within_bounds(sx, sy)) { + yield this.cells[sy][sx]; + } + sx += direction[0]; + sy += direction[1]; + } + } + } + } + // ------------------------------------------------------------------------- // Undo handling diff --git a/js/main-editor.js b/js/main-editor.js index f3fda1a..a2e838e 100644 --- a/js/main-editor.js +++ b/js/main-editor.js @@ -524,6 +524,7 @@ const EDITOR_PALETTE = [{ 'button_blue', 'button_red', 'cloner', 'button_brown', 'trap', + 'button_orange', 'flame_jet_off', 'flame_jet_on', 'teleport_blue', 'teleport_red', 'teleport_green', diff --git a/js/tiletypes.js b/js/tiletypes.js index b41685e..a4aed66 100644 --- a/js/tiletypes.js +++ b/js/tiletypes.js @@ -565,7 +565,7 @@ const TILE_TYPES = { blocks_all: true, is_actor: true, is_block: true, - ignores: new Set(['fire']), + ignores: new Set(['fire', 'flame_jet_on']), movement_speed: 4, }, ice_block: { @@ -583,14 +583,13 @@ const TILE_TYPES = { directional_block: { // TODO directional, obviously // TODO floor in water - // TODO destroyed in fire, flame jet, slime + // TODO destroyed in slime // TODO rotate on train tracks draw_layer: LAYER_ACTOR, blocks_all: true, is_actor: true, is_block: true, can_reveal_walls: true, - ignores: new Set(['fire']), movement_speed: 4, pushes: { dirt_block: true, @@ -857,6 +856,10 @@ const TILE_TYPES = { return level.iter_tiles_in_reading_order(me.cell, 'teleport_yellow', true); }, }, + // Flame jet rules: + // - State toggles /while/ an orange button is held or wire current is received + // - Multiple such inputs cancel each other out + // - Gray button toggles it permanently flame_jet_off: { draw_layer: LAYER_TERRAIN, activate(me, level) { @@ -871,7 +874,6 @@ const TILE_TYPES = { }, flame_jet_on: { draw_layer: LAYER_TERRAIN, - // FIXME every tic, kills every actor in the cell activate(me, level) { level.transmute_tile(me, 'flame_jet_off'); }, @@ -881,6 +883,21 @@ const TILE_TYPES = { on_power(me, level) { me.type.activate(me, level); }, + // Kill anything that shows up + // FIXME every tic, also kills every actor in the cell (mostly matters if you step on with + // fire boots and then drop them) + on_arrive(me, level, other) { + // Note that blocks, fireballs, and anything with fire boots are immune + // TODO would be neat if this understood "ignores anything with fire immunity" but that + // might be a bit too high-level for this game + if (other.type.is_player) { + level.fail('burned'); + } + else { + // TODO should this play a sound? + level.transmute_tile(other, 'explosion'); + } + }, }, // Buttons button_blue: { @@ -999,7 +1016,26 @@ const TILE_TYPES = { }, button_orange: { draw_layer: LAYER_TERRAIN, - // FIXME toggles flame jets, connected somehow, ??? + connects_to: new Set(['flame_jet_off', 'flame_jet_on']), + connect_order: 'diamond', + // Both stepping on and leaving the button have the same effect: toggle the state of the + // connected flame jet + _toggle_flame_jet(me, level, other) { + let jet = me.connection; + if (jet && jet.cell) { + jet.type.activate(jet, level); + } + }, + on_arrive(me, level, other) { + level.sfx.play_once('button-press', me.cell); + + me.type._toggle_flame_jet(me, level, other); + }, + on_depart(me, level, other) { + level.sfx.play_once('button-release', me.cell); + + me.type._toggle_flame_jet(me, level, other); + }, }, button_pink: { draw_layer: LAYER_TERRAIN, @@ -1164,7 +1200,7 @@ const TILE_TYPES = { blocks_blocks: true, movement_mode: 'turn-right', movement_speed: 4, - ignores: new Set(['fire']), + ignores: new Set(['fire', 'flame_jet_on']), }, glider: { draw_layer: LAYER_ACTOR, @@ -1279,7 +1315,7 @@ const TILE_TYPES = { is_tool: true, blocks_monsters: true, blocks_blocks: true, - item_ignores: new Set(['fire']), + item_ignores: new Set(['fire', 'flame_jet_on']), }, flippers: { draw_layer: LAYER_ITEM,