Merge remote-tracking branch 'upstream/master'
This commit is contained in:
commit
32b4399683
70
js/game.js
70
js/game.js
@ -81,7 +81,6 @@ export class Tile {
|
|||||||
can_push(tile) {
|
can_push(tile) {
|
||||||
return (
|
return (
|
||||||
this.type.pushes && this.type.pushes[tile.type.name] &&
|
this.type.pushes && this.type.pushes[tile.type.name] &&
|
||||||
tile.movement_cooldown === 0 &&
|
|
||||||
! tile.stuck);
|
! tile.stuck);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -291,22 +290,25 @@ export class Level {
|
|||||||
let cell = connectable.cell;
|
let cell = connectable.cell;
|
||||||
let x = cell.x;
|
let x = cell.x;
|
||||||
let y = cell.y;
|
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
|
// 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) {
|
if (this.stored_level.has_custom_connections) {
|
||||||
let n = this.stored_level.coords_to_scalar(x, y);
|
let n = this.stored_level.coords_to_scalar(x, y);
|
||||||
let target_cell_n = null;
|
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;
|
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;
|
target_cell_n = this.stored_level.custom_cloner_wiring[n] ?? null;
|
||||||
}
|
}
|
||||||
if (target_cell_n && target_cell_n < this.width * this.height) {
|
if (target_cell_n && target_cell_n < this.width * this.height) {
|
||||||
let [tx, ty] = this.stored_level.scalar_to_coords(target_cell_n);
|
let [tx, ty] = this.stored_level.scalar_to_coords(target_cell_n);
|
||||||
for (let tile of this.cells[ty][tx]) {
|
for (let tile of this.cells[ty][tx]) {
|
||||||
if (tile.type.name === goal) {
|
if (goals === tile.type.name) {
|
||||||
connectable.connection = tile;
|
connectable.connection = tile;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -315,8 +317,26 @@ export class Level {
|
|||||||
continue;
|
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
|
// 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
|
// TODO ideally this should be a weak connection somehow, since dynamite can destroy
|
||||||
// empty cloners and probably traps too
|
// empty cloners and probably traps too
|
||||||
connectable.connection = tile;
|
connectable.connection = tile;
|
||||||
@ -504,7 +524,6 @@ export class Level {
|
|||||||
for (let i = this.actors.length - 1; i >= 0; i--) {
|
for (let i = this.actors.length - 1; i >= 0; i--) {
|
||||||
let actor = this.actors[i];
|
let actor = this.actors[i];
|
||||||
if (actor != this.player)
|
if (actor != this.player)
|
||||||
continue;
|
|
||||||
{
|
{
|
||||||
this.actor_decision(actor, p1_primary_direction);
|
this.actor_decision(actor, p1_primary_direction);
|
||||||
}
|
}
|
||||||
@ -613,7 +632,7 @@ export class Level {
|
|||||||
|
|
||||||
// Teeth can only move the first 4 of every 8 tics, though "first"
|
// Teeth can only move the first 4 of every 8 tics, though "first"
|
||||||
// can be adjusted
|
// can be adjusted
|
||||||
if (actor.slide_mode == null &&
|
if (actor.slide_mode === null &&
|
||||||
actor.type.uses_teeth_hesitation &&
|
actor.type.uses_teeth_hesitation &&
|
||||||
(this.tic_counter + this.step_parity) % 8 >= 4)
|
(this.tic_counter + this.step_parity) % 8 >= 4)
|
||||||
{
|
{
|
||||||
@ -626,6 +645,13 @@ export class Level {
|
|||||||
{
|
{
|
||||||
this._set_prop(actor, 'pending_reverse', false);
|
this._set_prop(actor, 'pending_reverse', false);
|
||||||
}
|
}
|
||||||
|
// Blocks that were pushed while sliding will move in the push direction as soon as they
|
||||||
|
// stop sliding, regardless of what they landed on
|
||||||
|
if (actor.pending_push) {
|
||||||
|
actor.decision = actor.pending_push;
|
||||||
|
this._set_prop(actor, 'pending_push', null);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
if (actor.slide_mode === 'ice') {
|
if (actor.slide_mode === 'ice') {
|
||||||
// Actors can't make voluntary moves on ice; they just slide
|
// Actors can't make voluntary moves on ice; they just slide
|
||||||
actor.decision = actor.direction;
|
actor.decision = actor.direction;
|
||||||
@ -863,7 +889,13 @@ export class Level {
|
|||||||
// This time make a copy, since we're modifying the contents of the cell
|
// This time make a copy, since we're modifying the contents of the cell
|
||||||
for (let tile of Array.from(goal_cell)) {
|
for (let tile of Array.from(goal_cell)) {
|
||||||
if (actor.can_push(tile)) {
|
if (actor.can_push(tile)) {
|
||||||
this.attempt_step(tile, direction);
|
if (! this.attempt_step(tile, direction) &&
|
||||||
|
tile.slide_mode !== null && tile.movement_cooldown !== 0)
|
||||||
|
{
|
||||||
|
// If the push failed and the obstacle is in the middle of a slide,
|
||||||
|
// remember this as the next move it'll make
|
||||||
|
this._set_prop(tile, 'pending_push', direction);
|
||||||
|
}
|
||||||
if (actor === this.player) {
|
if (actor === this.player) {
|
||||||
actor.is_pushing = true;
|
actor.is_pushing = true;
|
||||||
}
|
}
|
||||||
@ -1223,6 +1255,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
|
// Undo handling
|
||||||
|
|
||||||
|
|||||||
@ -524,6 +524,7 @@ const EDITOR_PALETTE = [{
|
|||||||
'button_blue',
|
'button_blue',
|
||||||
'button_red', 'cloner',
|
'button_red', 'cloner',
|
||||||
'button_brown', 'trap',
|
'button_brown', 'trap',
|
||||||
|
'button_orange', 'flame_jet_off', 'flame_jet_on',
|
||||||
'teleport_blue',
|
'teleport_blue',
|
||||||
'teleport_red',
|
'teleport_red',
|
||||||
'teleport_green',
|
'teleport_green',
|
||||||
|
|||||||
@ -565,7 +565,7 @@ const TILE_TYPES = {
|
|||||||
blocks_all: true,
|
blocks_all: true,
|
||||||
is_actor: true,
|
is_actor: true,
|
||||||
is_block: true,
|
is_block: true,
|
||||||
ignores: new Set(['fire']),
|
ignores: new Set(['fire', 'flame_jet_on']),
|
||||||
movement_speed: 4,
|
movement_speed: 4,
|
||||||
},
|
},
|
||||||
ice_block: {
|
ice_block: {
|
||||||
@ -583,14 +583,13 @@ const TILE_TYPES = {
|
|||||||
directional_block: {
|
directional_block: {
|
||||||
// TODO directional, obviously
|
// TODO directional, obviously
|
||||||
// TODO floor in water
|
// TODO floor in water
|
||||||
// TODO destroyed in fire, flame jet, slime
|
// TODO destroyed in slime
|
||||||
// TODO rotate on train tracks
|
// TODO rotate on train tracks
|
||||||
draw_layer: LAYER_ACTOR,
|
draw_layer: LAYER_ACTOR,
|
||||||
blocks_all: true,
|
blocks_all: true,
|
||||||
is_actor: true,
|
is_actor: true,
|
||||||
is_block: true,
|
is_block: true,
|
||||||
can_reveal_walls: true,
|
can_reveal_walls: true,
|
||||||
ignores: new Set(['fire']),
|
|
||||||
movement_speed: 4,
|
movement_speed: 4,
|
||||||
pushes: {
|
pushes: {
|
||||||
dirt_block: true,
|
dirt_block: true,
|
||||||
@ -857,6 +856,10 @@ const TILE_TYPES = {
|
|||||||
return level.iter_tiles_in_reading_order(me.cell, 'teleport_yellow', true);
|
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: {
|
flame_jet_off: {
|
||||||
draw_layer: LAYER_TERRAIN,
|
draw_layer: LAYER_TERRAIN,
|
||||||
activate(me, level) {
|
activate(me, level) {
|
||||||
@ -871,7 +874,6 @@ const TILE_TYPES = {
|
|||||||
},
|
},
|
||||||
flame_jet_on: {
|
flame_jet_on: {
|
||||||
draw_layer: LAYER_TERRAIN,
|
draw_layer: LAYER_TERRAIN,
|
||||||
// FIXME every tic, kills every actor in the cell
|
|
||||||
activate(me, level) {
|
activate(me, level) {
|
||||||
level.transmute_tile(me, 'flame_jet_off');
|
level.transmute_tile(me, 'flame_jet_off');
|
||||||
},
|
},
|
||||||
@ -881,6 +883,21 @@ const TILE_TYPES = {
|
|||||||
on_power(me, level) {
|
on_power(me, level) {
|
||||||
me.type.activate(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
|
// Buttons
|
||||||
button_blue: {
|
button_blue: {
|
||||||
@ -999,7 +1016,26 @@ const TILE_TYPES = {
|
|||||||
},
|
},
|
||||||
button_orange: {
|
button_orange: {
|
||||||
draw_layer: LAYER_TERRAIN,
|
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: {
|
button_pink: {
|
||||||
draw_layer: LAYER_TERRAIN,
|
draw_layer: LAYER_TERRAIN,
|
||||||
@ -1164,7 +1200,7 @@ const TILE_TYPES = {
|
|||||||
blocks_blocks: true,
|
blocks_blocks: true,
|
||||||
movement_mode: 'turn-right',
|
movement_mode: 'turn-right',
|
||||||
movement_speed: 4,
|
movement_speed: 4,
|
||||||
ignores: new Set(['fire']),
|
ignores: new Set(['fire', 'flame_jet_on']),
|
||||||
},
|
},
|
||||||
glider: {
|
glider: {
|
||||||
draw_layer: LAYER_ACTOR,
|
draw_layer: LAYER_ACTOR,
|
||||||
@ -1279,7 +1315,7 @@ const TILE_TYPES = {
|
|||||||
is_tool: true,
|
is_tool: true,
|
||||||
blocks_monsters: true,
|
blocks_monsters: true,
|
||||||
blocks_blocks: true,
|
blocks_blocks: true,
|
||||||
item_ignores: new Set(['fire']),
|
item_ignores: new Set(['fire', 'flame_jet_on']),
|
||||||
},
|
},
|
||||||
flippers: {
|
flippers: {
|
||||||
draw_layer: LAYER_ITEM,
|
draw_layer: LAYER_ITEM,
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user