Merge remote-tracking branch 'upstream/master'

This commit is contained in:
Timothy Stiles 2020-10-26 16:05:34 +11:00
commit 32b4399683
3 changed files with 105 additions and 16 deletions

View File

@ -81,7 +81,6 @@ export class Tile {
can_push(tile) {
return (
this.type.pushes && this.type.pushes[tile.type.name] &&
tile.movement_cooldown === 0 &&
! tile.stuck);
}
@ -291,22 +290,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 +317,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;
@ -504,7 +524,6 @@ export class Level {
for (let i = this.actors.length - 1; i >= 0; i--) {
let actor = this.actors[i];
if (actor != this.player)
continue;
{
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"
// can be adjusted
if (actor.slide_mode == null &&
if (actor.slide_mode === null &&
actor.type.uses_teeth_hesitation &&
(this.tic_counter + this.step_parity) % 8 >= 4)
{
@ -626,6 +645,13 @@ export class Level {
{
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') {
// Actors can't make voluntary moves on ice; they just slide
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
for (let tile of Array.from(goal_cell)) {
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) {
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

View File

@ -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',

View File

@ -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,