Rewrite wiring code and fix basically all issues with it; faster, undoable, etc.
This commit is contained in:
parent
48f085d0df
commit
78f59b38c1
109
js/algorithms.js
Normal file
109
js/algorithms.js
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
import { DIRECTIONS, DIRECTION_ORDER } from './defs.js';
|
||||||
|
|
||||||
|
export function trace_floor_circuit(level, start_cell, start_edge, on_wire, on_dead_end) {
|
||||||
|
let is_first = true;
|
||||||
|
let pending = [[start_cell, start_edge]];
|
||||||
|
let seen_cells = new Map;
|
||||||
|
while (pending.length > 0) {
|
||||||
|
let next = [];
|
||||||
|
for (let [cell, edge] of pending) {
|
||||||
|
let terrain = cell.get_terrain();
|
||||||
|
if (! terrain)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
let edgeinfo = DIRECTIONS[edge];
|
||||||
|
let seen_edges = seen_cells.get(cell) ?? 0;
|
||||||
|
if (seen_edges & edgeinfo.bit)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// The wire comes in from this edge towards the center; see how it connects within this
|
||||||
|
// cell, then check for any neighbors
|
||||||
|
let connections = edgeinfo.bit;
|
||||||
|
if (! is_first && ((terrain.wire_directions ?? 0) & edgeinfo.bit) === 0) {
|
||||||
|
// There's not actually a wire here (but not if this is our starting cell, in which
|
||||||
|
// case we trust the caller)
|
||||||
|
if (on_dead_end) {
|
||||||
|
on_dead_end(terrain.cell, edge);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else if (terrain.type.wire_propagation_mode === 'none') {
|
||||||
|
// The wires in this tile never connect to each other
|
||||||
|
}
|
||||||
|
else if (terrain.wire_directions === 0x0f && terrain.type.wire_propagation_mode !== 'all') {
|
||||||
|
// This is a cross pattern, so only opposite edges connect
|
||||||
|
connections |= edgeinfo.opposite_bit;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Everything connects
|
||||||
|
connections |= terrain.wire_directions;
|
||||||
|
}
|
||||||
|
|
||||||
|
seen_cells.set(cell, seen_edges | connections);
|
||||||
|
|
||||||
|
if (on_wire) {
|
||||||
|
on_wire(terrain, connections);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let [direction, dirinfo] of Object.entries(DIRECTIONS)) {
|
||||||
|
// Obviously don't go backwards, but that doesn't apply if this is our first pass
|
||||||
|
if (direction === edge && ! is_first)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if ((connections & dirinfo.bit) === 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
let neighbor;
|
||||||
|
if ((terrain.wire_tunnel_directions ?? 0) & dirinfo.bit) {
|
||||||
|
// Search in this direction for a matching tunnel
|
||||||
|
neighbor = find_matching_wire_tunnel(level, cell.x, cell.y, direction);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
neighbor = level.get_neighboring_cell(cell, direction);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
if (! neighbor || (((neighbor.get_terrain().wire_directions ?? 0) & dirinfo.opposite_bit) === 0)) {
|
||||||
|
console.log("bailing here", neighbor, direction);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
if (! neighbor)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
next.push([neighbor, dirinfo.opposite]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pending = next;
|
||||||
|
is_first = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function find_matching_wire_tunnel(level, x, y, direction) {
|
||||||
|
let dirinfo = DIRECTIONS[direction];
|
||||||
|
let [dx, dy] = dirinfo.movement;
|
||||||
|
let nesting = 0;
|
||||||
|
while (true) {
|
||||||
|
x += dx;
|
||||||
|
y += dy;
|
||||||
|
let candidate = level.cell(x, y);
|
||||||
|
if (! candidate)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
let neighbor = candidate.get_terrain();
|
||||||
|
if (! neighbor)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if ((neighbor.wire_tunnel_directions ?? 0) & dirinfo.opposite_bit) {
|
||||||
|
if (nesting === 0) {
|
||||||
|
return candidate;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
nesting -= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ((neighbor.wire_tunnel_directions ?? 0) & dirinfo.bit) {
|
||||||
|
nesting += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -4,6 +4,7 @@ export const DIRECTIONS = {
|
|||||||
north: {
|
north: {
|
||||||
movement: [0, -1],
|
movement: [0, -1],
|
||||||
bit: 0x01,
|
bit: 0x01,
|
||||||
|
opposite_bit: 0x04,
|
||||||
index: 0,
|
index: 0,
|
||||||
action: 'up',
|
action: 'up',
|
||||||
left: 'west',
|
left: 'west',
|
||||||
@ -13,6 +14,7 @@ export const DIRECTIONS = {
|
|||||||
south: {
|
south: {
|
||||||
movement: [0, 1],
|
movement: [0, 1],
|
||||||
bit: 0x04,
|
bit: 0x04,
|
||||||
|
opposite_bit: 0x01,
|
||||||
index: 2,
|
index: 2,
|
||||||
action: 'down',
|
action: 'down',
|
||||||
left: 'east',
|
left: 'east',
|
||||||
@ -22,6 +24,7 @@ export const DIRECTIONS = {
|
|||||||
west: {
|
west: {
|
||||||
movement: [-1, 0],
|
movement: [-1, 0],
|
||||||
bit: 0x08,
|
bit: 0x08,
|
||||||
|
opposite_bit: 0x02,
|
||||||
index: 3,
|
index: 3,
|
||||||
action: 'left',
|
action: 'left',
|
||||||
left: 'south',
|
left: 'south',
|
||||||
@ -31,6 +34,7 @@ export const DIRECTIONS = {
|
|||||||
east: {
|
east: {
|
||||||
movement: [1, 0],
|
movement: [1, 0],
|
||||||
bit: 0x02,
|
bit: 0x02,
|
||||||
|
opposite_bit: 0x08,
|
||||||
index: 1,
|
index: 1,
|
||||||
action: 'right',
|
action: 'right',
|
||||||
left: 'north',
|
left: 'north',
|
||||||
|
|||||||
346
js/game.js
346
js/game.js
@ -1,3 +1,4 @@
|
|||||||
|
import * as algorithms from './algorithms.js';
|
||||||
import { DIRECTIONS, DIRECTION_ORDER, INPUT_BITS, TICS_PER_SECOND } from './defs.js';
|
import { DIRECTIONS, DIRECTION_ORDER, INPUT_BITS, TICS_PER_SECOND } from './defs.js';
|
||||||
import { LevelInterface } from './format-base.js';
|
import { LevelInterface } from './format-base.js';
|
||||||
import TILE_TYPES from './tiletypes.js';
|
import TILE_TYPES from './tiletypes.js';
|
||||||
@ -117,6 +118,9 @@ export class Tile {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Tile.prototype.emitting_edges = 0;
|
Tile.prototype.emitting_edges = 0;
|
||||||
|
Tile.prototype.powered_edges = 0;
|
||||||
|
Tile.prototype.wire_directions = 0;
|
||||||
|
Tile.prototype.wire_tunnel_directions = 0;
|
||||||
|
|
||||||
export class Cell extends Array {
|
export class Cell extends Array {
|
||||||
constructor(x, y) {
|
constructor(x, y) {
|
||||||
@ -294,8 +298,6 @@ export class Cell extends Array {
|
|||||||
return direction;
|
return direction;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Cell.prototype.prev_powered_edges = 0;
|
|
||||||
Cell.prototype.powered_edges = 0;
|
|
||||||
|
|
||||||
// The undo stack is implemented with a ring buffer, and this is its size. One entry per tic.
|
// The undo stack is implemented with a ring buffer, and this is its size. One entry per tic.
|
||||||
// Based on Chrome measurements made against the pathological level CCLP4 #40 (Periodic Lasers) and
|
// Based on Chrome measurements made against the pathological level CCLP4 #40 (Periodic Lasers) and
|
||||||
@ -376,7 +378,6 @@ export class Level extends LevelInterface {
|
|||||||
|
|
||||||
let n = 0;
|
let n = 0;
|
||||||
let connectables = [];
|
let connectables = [];
|
||||||
this.power_sources = [];
|
|
||||||
this.players = [];
|
this.players = [];
|
||||||
// FIXME handle traps correctly:
|
// FIXME handle traps correctly:
|
||||||
// - if an actor is in the cell, set the trap to open and unstick everything in it
|
// - if an actor is in the cell, set the trap to open and unstick everything in it
|
||||||
@ -397,10 +398,6 @@ export class Level extends LevelInterface {
|
|||||||
tile.hint_text = template_tile.hint_text ?? null;
|
tile.hint_text = template_tile.hint_text ?? null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tile.type.is_power_source) {
|
|
||||||
this.power_sources.push(tile);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tile.type.is_real_player) {
|
if (tile.type.is_real_player) {
|
||||||
this.players.push(tile);
|
this.players.push(tile);
|
||||||
}
|
}
|
||||||
@ -484,6 +481,111 @@ export class Level extends LevelInterface {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Build circuits out of connected wires
|
||||||
|
// TODO document this idea
|
||||||
|
this.circuits = [];
|
||||||
|
this.power_sources = [];
|
||||||
|
let wired_outputs = new Set;
|
||||||
|
this.wired_outputs = [];
|
||||||
|
let add_to_edge_map = (map, item, edges) => {
|
||||||
|
map.set(item, (map.get(item) ?? 0) | edges);
|
||||||
|
};
|
||||||
|
for (let cell of this.linear_cells) {
|
||||||
|
// We're interested in static circuitry, which means terrain
|
||||||
|
let terrain = cell.get_terrain();
|
||||||
|
if (! terrain) // ?!
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (terrain.type.is_power_source) {
|
||||||
|
this.power_sources.push(terrain);
|
||||||
|
}
|
||||||
|
|
||||||
|
let wire_directions = terrain.wire_directions;
|
||||||
|
if (! wire_directions && ! terrain.wire_tunnel_directions) {
|
||||||
|
// No wires, not interesting... unless it's a logic gate, which defines its own
|
||||||
|
// wires! We only care about outgoing ones here, on the off chance that they point
|
||||||
|
// directly into a non-wired tile, in which case a wire scan won't find them
|
||||||
|
if (terrain.type.name === 'logic_gate') {
|
||||||
|
let dir = terrain.direction;
|
||||||
|
let cxns = terrain.type._gate_types[terrain.gate_type];
|
||||||
|
for (let i = 0; i < 4; i++) {
|
||||||
|
let cxn = cxns[i];
|
||||||
|
if (cxn && cxn.match(/^out/)) {
|
||||||
|
wire_directions |= DIRECTIONS[dir].bit;
|
||||||
|
}
|
||||||
|
dir = DIRECTIONS[dir].right;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let [direction, dirinfo] of Object.entries(DIRECTIONS)) {
|
||||||
|
if (! ((wire_directions | terrain.wire_tunnel_directions) & dirinfo.bit))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (terrain.circuits && terrain.circuits[dirinfo.index])
|
||||||
|
continue;
|
||||||
|
|
||||||
|
let circuit = {
|
||||||
|
is_powered: false,
|
||||||
|
tiles: new Map,
|
||||||
|
inputs: new Map,
|
||||||
|
};
|
||||||
|
this.circuits.push(circuit);
|
||||||
|
// At last, a wired cell edge we have not yet handled. Floodfill from here
|
||||||
|
algorithms.trace_floor_circuit(
|
||||||
|
this, terrain.cell, direction,
|
||||||
|
// Wire handling
|
||||||
|
(tile, edges) => {
|
||||||
|
if (! tile.circuits) {
|
||||||
|
tile.circuits = [null, null, null, null];
|
||||||
|
}
|
||||||
|
for (let [direction, dirinfo] of Object.entries(DIRECTIONS)) {
|
||||||
|
if (edges & dirinfo.bit) {
|
||||||
|
tile.circuits[dirinfo.index] = circuit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
add_to_edge_map(circuit.tiles, tile, edges);
|
||||||
|
|
||||||
|
if (tile.type.is_power_source) {
|
||||||
|
// TODO could just do this in a pass afterwards
|
||||||
|
add_to_edge_map(circuit.inputs, tile, edges);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// Dead end handling (potentially logic gates, etc.)
|
||||||
|
(cell, edge) => {
|
||||||
|
for (let tile of cell) {
|
||||||
|
if (tile.type.name === 'logic_gate') {
|
||||||
|
// Logic gates are the one non-wired tile that get attached to circuits,
|
||||||
|
// mostly so blue teleporters can follow them
|
||||||
|
if (! tile.circuits) {
|
||||||
|
tile.circuits = [null, null, null, null];
|
||||||
|
}
|
||||||
|
tile.circuits[DIRECTIONS[edge].index] = circuit;
|
||||||
|
|
||||||
|
let wire = tile.type._gate_types[tile.gate_type][
|
||||||
|
(DIRECTIONS[edge].index - DIRECTIONS[tile.direction].index + 4) % 4];
|
||||||
|
if (! wire)
|
||||||
|
return;
|
||||||
|
add_to_edge_map(circuit.tiles, tile, DIRECTIONS[edge].bit);
|
||||||
|
if (wire.match(/^out/)) {
|
||||||
|
add_to_edge_map(circuit.inputs, tile, DIRECTIONS[edge].bit);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (tile.type.on_power) {
|
||||||
|
add_to_edge_map(circuit.tiles, tile, DIRECTIONS[edge].bit);
|
||||||
|
wired_outputs.add(tile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.wired_outputs = Array.from(wired_outputs);
|
||||||
|
this.wired_outputs.sort((a, b) => this.coords_to_scalar(a.cell.x, a.cell.y) - this.coords_to_scalar(b.cell.x, b.cell.y));
|
||||||
|
|
||||||
// Finally, let all tiles do any custom init behavior
|
// Finally, let all tiles do any custom init behavior
|
||||||
for (let cell of this.linear_cells) {
|
for (let cell of this.linear_cells) {
|
||||||
for (let tile of cell) {
|
for (let tile of cell) {
|
||||||
@ -569,9 +671,8 @@ export class Level extends LevelInterface {
|
|||||||
this.p1_released |= ~p1_input; // Action keys released since we last checked them
|
this.p1_released |= ~p1_input; // Action keys released since we last checked them
|
||||||
this.swap_player1 = false;
|
this.swap_player1 = false;
|
||||||
|
|
||||||
// Used for various tic-local effects; don't need to be undoable
|
// This effect only lasts one tic, after which we can move again
|
||||||
// TODO maybe this should be undone anyway so rewind looks better?
|
this._set_tile_prop(this.player, 'is_blocked', false);
|
||||||
this.player.is_blocked = false;
|
|
||||||
|
|
||||||
this.sfx.set_player_position(this.player.cell);
|
this.sfx.set_player_position(this.player.cell);
|
||||||
|
|
||||||
@ -720,7 +821,7 @@ export class Level extends LevelInterface {
|
|||||||
// Track whether the player is blocked, for visual effect
|
// Track whether the player is blocked, for visual effect
|
||||||
if (actor === this.player && actor.decision && ! success) {
|
if (actor === this.player && actor.decision && ! success) {
|
||||||
this.sfx.play_once('blocked');
|
this.sfx.play_once('blocked');
|
||||||
actor.is_blocked = true;
|
this._set_tile_prop(actor, 'is_blocked', true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1397,178 +1498,130 @@ export class Level extends LevelInterface {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update the state of all wired tiles in the game.
|
|
||||||
// XXX need to be clear on the order of events here. say everything starts out unpowered.
|
|
||||||
// then:
|
|
||||||
// 1. you step on a pink button, which flags itself as going to be powered next frame
|
|
||||||
// 2. this pass happens. every unpowered-but-wired cell is inspected. if a powered one is
|
|
||||||
// found, floodfill from there
|
|
||||||
// FIXME can probably skip this if we know there are no wires at all, like in a CCL, or just an
|
|
||||||
// unwired map
|
|
||||||
// FIXME this feels inefficient. most of the time none of the inputs have changed so none of
|
|
||||||
// this needs to happen at all
|
|
||||||
// FIXME none of this is currently undoable
|
|
||||||
update_wiring() {
|
update_wiring() {
|
||||||
// FIXME:
|
if (this.circuits.length === 0)
|
||||||
// - make this undoable :(
|
return;
|
||||||
// - blue tele, red tele, and pink button have different connections
|
|
||||||
// - would like to reuse the walk for blue teles
|
|
||||||
|
|
||||||
// Gather every tile that's emitting power. Along the way, check whether any of them have
|
// Prepare a big slab of undo. The only thing we directly change here (aside from
|
||||||
// changed since last tic, so we can skip this work entirely if none did
|
// emitting_edges, a normal tile property) is Tile.powered_edges, which tends to change for
|
||||||
let neighbors = [];
|
// large numbers of tiles at a time, so store it all in one map and undo it in one shot.
|
||||||
|
let powered_edges_changes = new Map;
|
||||||
|
let _set_edges = (tile, new_edges) => {
|
||||||
|
if (powered_edges_changes.has(tile)) {
|
||||||
|
if (powered_edges_changes.get(tile) === new_edges) {
|
||||||
|
powered_edges_changes.delete(tile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
powered_edges_changes.set(tile, tile.powered_edges);
|
||||||
|
}
|
||||||
|
tile.powered_edges = new_edges;
|
||||||
|
};
|
||||||
|
let power_edges = (tile, edges) => {
|
||||||
|
let new_edges = tile.powered_edges | edges;
|
||||||
|
_set_edges(tile, new_edges);
|
||||||
|
};
|
||||||
|
let depower_edges = (tile, edges) => {
|
||||||
|
let new_edges = tile.powered_edges & ~edges;
|
||||||
|
_set_edges(tile, new_edges);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Update the state of any tiles that can generate power. If none of them changed since
|
||||||
|
// last wiring update, stop here. First, static power sources.
|
||||||
let any_changed = false;
|
let any_changed = false;
|
||||||
for (let tile of this.power_sources) {
|
for (let tile of this.power_sources) {
|
||||||
if (! tile.cell)
|
if (! tile.cell)
|
||||||
continue;
|
continue;
|
||||||
let emitting = tile.type.get_emitting_edges(tile, this);
|
let emitting = tile.type.get_emitting_edges(tile, this);
|
||||||
if (emitting) {
|
|
||||||
neighbors.push([tile.cell, emitting]);
|
|
||||||
}
|
|
||||||
if (emitting !== tile.emitting_edges) {
|
if (emitting !== tile.emitting_edges) {
|
||||||
any_changed = true;
|
any_changed = true;
|
||||||
tile.emitting_edges = emitting;
|
this._set_tile_prop(tile, 'emitting_edges', emitting);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Also check actors, since any of them might be holding a lightning bolt (argh)
|
// Next, actors who are standing still, on floor, and holding a lightning bolt
|
||||||
|
let externally_powered_circuits = new Set;
|
||||||
for (let actor of this.actors) {
|
for (let actor of this.actors) {
|
||||||
if (! actor.cell)
|
if (! actor.cell)
|
||||||
continue;
|
continue;
|
||||||
// Only count when they're on a floor tile AND not in transit!
|
let emitting = 0;
|
||||||
let emitting = null;
|
|
||||||
if (actor.movement_cooldown === 0 && actor.has_item('lightning_bolt')) {
|
if (actor.movement_cooldown === 0 && actor.has_item('lightning_bolt')) {
|
||||||
let wired_tile = actor.cell.get_wired_tile();
|
let wired_tile = actor.cell.get_wired_tile();
|
||||||
if (wired_tile && (wired_tile === actor || wired_tile.type.name === 'floor')) {
|
if (wired_tile && (wired_tile === actor || wired_tile.type.name === 'floor')) {
|
||||||
emitting = wired_tile.wire_directions;
|
emitting = wired_tile.wire_directions;
|
||||||
neighbors.push([actor.cell, wired_tile.wire_directions]);
|
for (let circuit of wired_tile.circuits) {
|
||||||
|
if (circuit) {
|
||||||
|
externally_powered_circuits.add(circuit);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (emitting !== actor.emitting_edges) {
|
if (emitting !== actor.emitting_edges) {
|
||||||
any_changed = true;
|
any_changed = true;
|
||||||
actor.emitting_edges = emitting;
|
this._set_tile_prop(actor, 'emitting_edges', emitting);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// If none changed, we're done
|
|
||||||
if (! any_changed)
|
if (! any_changed)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// Turn off power to every cell
|
for (let tile of this.wired_outputs) {
|
||||||
for (let cell of this.linear_cells) {
|
// This is only used within this function, no need to undo
|
||||||
cell.prev_powered_edges = cell.powered_edges;
|
// TODO if this can overlap with power_sources then this is too late?
|
||||||
cell.powered_edges = 0;
|
tile._prev_powered_edges = tile.powered_edges;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Iterate over emitters and flood-fill outwards one edge at a time
|
// Now go through every circuit, compute whether it's powered, and if that changed, inform
|
||||||
// propagated it via flood-fill through neighboring wires
|
// its outputs
|
||||||
while (neighbors.length > 0) {
|
let circuit_changes = new Map;
|
||||||
let [cell, source_direction] = neighbors.shift();
|
for (let circuit of this.circuits) {
|
||||||
let wire = cell.get_wired_tile();
|
let is_powered = false;
|
||||||
|
|
||||||
// Power this cell
|
if (externally_powered_circuits.has(circuit)) {
|
||||||
if (typeof(source_direction) === 'number') {
|
is_powered = true;
|
||||||
// This cell is emitting power itself, and the source direction is actually a
|
|
||||||
// bitmask of directions
|
|
||||||
cell.powered_edges = source_direction;
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
let bit = DIRECTIONS[source_direction].bit;
|
for (let [input_tile, edges] of circuit.inputs.entries()) {
|
||||||
if (wire === null || (wire.wire_directions & bit) === 0) {
|
if (input_tile.emitting_edges & edges) {
|
||||||
// No wire on this side, so the power doesn't actually propagate, but it DOES
|
is_powered = true;
|
||||||
// stay on this edge (so if this is e.g. a purple tile, it'll be powered)
|
break;
|
||||||
cell.powered_edges |= bit;
|
}
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Common case: power entering a wired edge and propagating outwards. There are a
|
|
||||||
// couple special cases:
|
|
||||||
if (wire.type.wire_propagation_mode === 'none') {
|
|
||||||
// This tile type has wires, but none of them connect to each other
|
|
||||||
cell.powered_edges |= bit;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
else if (wire.wire_directions === 0x0f && wire.type.wire_propagation_mode !== 'all') {
|
|
||||||
// If all four wires are present, they don't actually make a four-way
|
|
||||||
// connection, but two straight wires that don't connect to each other (with the
|
|
||||||
// exception of blue teleporters)
|
|
||||||
cell.powered_edges |= bit;
|
|
||||||
cell.powered_edges |= DIRECTIONS[DIRECTIONS[source_direction].opposite].bit;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
cell.powered_edges = wire.wire_directions;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Propagate current to neighbors
|
let was_powered = circuit.is_powered;
|
||||||
for (let [direction, dirinfo] of Object.entries(DIRECTIONS)) {
|
if (is_powered === was_powered)
|
||||||
if (direction === source_direction)
|
continue;
|
||||||
continue;
|
|
||||||
if ((cell.powered_edges & dirinfo.bit) === 0)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
let neighbor, neighbor_wire;
|
circuit.is_powered = is_powered;
|
||||||
let opposite_bit = DIRECTIONS[dirinfo.opposite].bit;
|
circuit_changes.set(circuit, was_powered);
|
||||||
if (wire && (wire.wire_tunnel_directions & dirinfo.bit)) {
|
|
||||||
// Search in the given direction until we find a matching tunnel
|
for (let [tile, edges] of circuit.tiles.entries()) {
|
||||||
let x = cell.x;
|
if (is_powered) {
|
||||||
let y = cell.y;
|
power_edges(tile, edges);
|
||||||
let nesting = 0;
|
|
||||||
while (true) {
|
|
||||||
x += dirinfo.movement[0];
|
|
||||||
y += dirinfo.movement[1];
|
|
||||||
let candidate = this.cell(x, y);
|
|
||||||
if (! candidate)
|
|
||||||
break;
|
|
||||||
neighbor_wire = candidate.get_wired_tile();
|
|
||||||
if (neighbor_wire) {
|
|
||||||
if ((neighbor_wire.wire_tunnel_directions ?? 0) & opposite_bit) {
|
|
||||||
if (nesting === 0) {
|
|
||||||
neighbor = candidate;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
nesting -= 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ((neighbor_wire.wire_tunnel_directions ?? 0) & dirinfo.bit) {
|
|
||||||
nesting += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// No tunnel; this is easy
|
depower_edges(tile, edges);
|
||||||
neighbor = this.get_neighboring_cell(cell, direction);
|
|
||||||
if (neighbor) {
|
|
||||||
neighbor_wire = neighbor.get_wired_tile();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (neighbor && (neighbor.powered_edges & opposite_bit) === 0 &&
|
|
||||||
// Unwired tiles are OK; they might be something activated by power.
|
|
||||||
// Wired tiles that do NOT connect to us are ignored.
|
|
||||||
(! neighbor_wire || neighbor_wire.wire_directions & opposite_bit))
|
|
||||||
{
|
|
||||||
neighbors.push([neighbor, dirinfo.opposite]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Inform any affected cells of power changes
|
for (let tile of this.wired_outputs) {
|
||||||
for (let cell of this.linear_cells) {
|
if (tile.powered_edges && ! tile._prev_powered_edges && tile.type.on_power) {
|
||||||
if ((cell.prev_powered_edges === 0) !== (cell.powered_edges === 0)) {
|
tile.type.on_power(tile, this);
|
||||||
let method = cell.powered_edges ? 'on_power' : 'on_depower';
|
}
|
||||||
for (let tile of cell) {
|
else if (! tile.powered_edges && tile._prev_powered_edges && tile.type.on_depower) {
|
||||||
if (tile.type[method]) {
|
tile.type.on_depower(tile, this);
|
||||||
tile.type[method](tile, this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Performs a depth-first search for connected wires and wire objects, extending out from the
|
this.pending_undo.push(() => {
|
||||||
// given starting cell
|
for (let [tile, edges] of powered_edges_changes.entries()) {
|
||||||
*follow_circuit(cell) {
|
tile.powered_edges = edges;
|
||||||
|
}
|
||||||
|
for (let [circuit, is_powered] of circuit_changes.entries()) {
|
||||||
|
circuit.is_powered = is_powered;
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// -------------------------------------------------------------------------
|
// -------------------------------------------------------------------------
|
||||||
@ -1630,9 +1683,14 @@ export class Level extends LevelInterface {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
is_cell_wired(cell) {
|
// FIXME require_stub should really just care whether we ourselves /can/ contain wire, and also
|
||||||
for (let direction of Object.keys(DIRECTIONS)) {
|
// we should check that on our neighbor
|
||||||
let neighbor = this.get_neighboring_cell(cell, direction);
|
is_tile_wired(tile, require_stub = true) {
|
||||||
|
for (let [direction, dirinfo] of Object.entries(DIRECTIONS)) {
|
||||||
|
if (require_stub && (tile.wire_directions & dirinfo.bit) === 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
let neighbor = this.get_neighboring_cell(tile.cell, direction);
|
||||||
if (! neighbor)
|
if (! neighbor)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
@ -1640,7 +1698,11 @@ export class Level extends LevelInterface {
|
|||||||
if (! wired)
|
if (! wired)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (wired.wire_directions & DIRECTIONS[DIRECTIONS[direction].opposite].bit)
|
if (wired.type.wire_propagation_mode === 'none' && ! wired.type.is_power_source)
|
||||||
|
// Being next to e.g. a red teleporter doesn't count (but pink button is ok)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (wired.wire_directions & dirinfo.opposite_bit)
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@ -985,18 +985,18 @@ export class Tileset {
|
|||||||
this._draw_fourway_tile_power(tile, 0x0f, blit);
|
this._draw_fourway_tile_power(tile, 0x0f, blit);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
if (tile.cell.powered_edges & DIRECTIONS[tile.direction].bit) {
|
if (tile.powered_edges & DIRECTIONS[tile.direction].bit) {
|
||||||
// Output (on top)
|
// Output (on top)
|
||||||
let [x0, y0, x1, y1] = this._rotate(tile.direction, 0.5 - r, 0, 0.5 + r, 0.5);
|
let [x0, y0, x1, y1] = this._rotate(tile.direction, 0.5 - r, 0, 0.5 + r, 0.5);
|
||||||
blit(powered_coords[0], powered_coords[1], x0, y0, x1 - x0, y1 - y0);
|
blit(powered_coords[0], powered_coords[1], x0, y0, x1 - x0, y1 - y0);
|
||||||
}
|
}
|
||||||
if (tile.cell.powered_edges & DIRECTIONS[DIRECTIONS[tile.direction].right].bit) {
|
if (tile.powered_edges & DIRECTIONS[DIRECTIONS[tile.direction].right].bit) {
|
||||||
// Right input, which includes the middle
|
// Right input, which includes the middle
|
||||||
// This actually covers the entire lower right corner, for bent inputs.
|
// This actually covers the entire lower right corner, for bent inputs.
|
||||||
let [x0, y0, x1, y1] = this._rotate(tile.direction, 0.5 - r, 0.5 - r, 1, 1);
|
let [x0, y0, x1, y1] = this._rotate(tile.direction, 0.5 - r, 0.5 - r, 1, 1);
|
||||||
blit(powered_coords[0], powered_coords[1], x0, y0, x1 - x0, y1 - y0);
|
blit(powered_coords[0], powered_coords[1], x0, y0, x1 - x0, y1 - y0);
|
||||||
}
|
}
|
||||||
if (tile.cell.powered_edges & DIRECTIONS[DIRECTIONS[tile.direction].left].bit) {
|
if (tile.powered_edges & DIRECTIONS[DIRECTIONS[tile.direction].left].bit) {
|
||||||
// Left input, which does not include the middle
|
// Left input, which does not include the middle
|
||||||
// This actually covers the entire lower left corner, for bent inputs.
|
// This actually covers the entire lower left corner, for bent inputs.
|
||||||
let [x0, y0, x1, y1] = this._rotate(tile.direction, 0, 0.5 - r, 0.5 - r, 1);
|
let [x0, y0, x1, y1] = this._rotate(tile.direction, 0, 0.5 - r, 0.5 - r, 1);
|
||||||
@ -1017,7 +1017,7 @@ export class Tileset {
|
|||||||
_draw_fourway_tile_power(tile, wires, blit) {
|
_draw_fourway_tile_power(tile, wires, blit) {
|
||||||
// Draw the unpowered tile underneath, if any edge is unpowered (and in fact if /none/ of it
|
// Draw the unpowered tile underneath, if any edge is unpowered (and in fact if /none/ of it
|
||||||
// is powered then we're done here)
|
// is powered then we're done here)
|
||||||
let powered = (tile.cell ? tile.cell.powered_edges : 0) & wires;
|
let powered = (tile.cell ? tile.powered_edges : 0) & wires;
|
||||||
if (! tile.cell || powered !== tile.wire_directions) {
|
if (! tile.cell || powered !== tile.wire_directions) {
|
||||||
this._draw_fourway_power_underlay(this.layout['#unpowered'], wires, blit);
|
this._draw_fourway_power_underlay(this.layout['#unpowered'], wires, blit);
|
||||||
if (! tile.cell || powered === 0)
|
if (! tile.cell || powered === 0)
|
||||||
|
|||||||
115
js/tiletypes.js
115
js/tiletypes.js
@ -466,7 +466,7 @@ const TILE_TYPES = {
|
|||||||
level._set_tile_prop(me, 'entered_direction', other.direction);
|
level._set_tile_prop(me, 'entered_direction', other.direction);
|
||||||
},
|
},
|
||||||
on_depart(me, level, other) {
|
on_depart(me, level, other) {
|
||||||
if (! level.is_cell_wired(me.cell)) {
|
if (! level.is_tile_wired(me, false)) {
|
||||||
me.type._switch_track(me, level);
|
me.type._switch_track(me, level);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -1104,6 +1104,8 @@ const TILE_TYPES = {
|
|||||||
},
|
},
|
||||||
transmogrifier: {
|
transmogrifier: {
|
||||||
draw_layer: DRAW_LAYERS.terrain,
|
draw_layer: DRAW_LAYERS.terrain,
|
||||||
|
// C2M technically supports wires in transmogrifiers, but they don't do anything
|
||||||
|
wire_propagation_mode: 'none',
|
||||||
_mogrifications: {
|
_mogrifications: {
|
||||||
player: 'player2',
|
player: 'player2',
|
||||||
player2: 'player',
|
player2: 'player',
|
||||||
@ -1128,15 +1130,12 @@ const TILE_TYPES = {
|
|||||||
teeth_timid: 'teeth',
|
teeth_timid: 'teeth',
|
||||||
},
|
},
|
||||||
_blob_mogrifications: ['ball', 'walker', 'fireball', 'glider', 'paramecium', 'bug', 'tank_blue', 'teeth', 'teeth_timid'],
|
_blob_mogrifications: ['ball', 'walker', 'fireball', 'glider', 'paramecium', 'bug', 'tank_blue', 'teeth', 'teeth_timid'],
|
||||||
on_ready(me, level) {
|
|
||||||
me.is_powered = false;
|
|
||||||
},
|
|
||||||
on_arrive(me, level, other) {
|
on_arrive(me, level, other) {
|
||||||
// Note: Transmogrifiers technically contain wires the way teleports do, and CC2 uses
|
// Note: Transmogrifiers technically contain wires the way teleports do, and CC2 uses
|
||||||
// the presence and poweredness of those wires to determine whether the transmogrifier
|
// the presence and poweredness of those wires to determine whether the transmogrifier
|
||||||
// should appear to be on or off, but the /functionality/ is controlled entirely by
|
// should appear to be on or off, but the /functionality/ is controlled entirely by
|
||||||
// whether an adjoining cell carries current to our edge, like a railroad or cloner
|
// whether an adjoining cell carries current to our edge, like a railroad or cloner
|
||||||
if (level.is_cell_wired(me.cell) && ! me.is_powered)
|
if (level.is_tile_wired(me, false) && ! me.powered_edges)
|
||||||
return;
|
return;
|
||||||
let name = other.type.name;
|
let name = other.type.name;
|
||||||
if (me.type._mogrifications[name]) {
|
if (me.type._mogrifications[name]) {
|
||||||
@ -1149,30 +1148,33 @@ const TILE_TYPES = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
on_power(me, level) {
|
on_power(me, level) {
|
||||||
level._set_tile_prop(me, 'is_powered', true);
|
// No need to do anything, we just need this here as a signal that our .powered_edges
|
||||||
},
|
// needs to be updated
|
||||||
on_depower(me, level) {
|
|
||||||
level._set_tile_prop(me, 'is_powered', false);
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
// FIXME blue teleporters transmit current 4 ways. red don't transmit it at all
|
|
||||||
teleport_blue: {
|
teleport_blue: {
|
||||||
draw_layer: DRAW_LAYERS.terrain,
|
draw_layer: DRAW_LAYERS.terrain,
|
||||||
wire_propagation_mode: 'all',
|
wire_propagation_mode: 'all',
|
||||||
*teleport_dest_order(me, level, other) {
|
*teleport_dest_order(me, level, other) {
|
||||||
let exit_direction = other.direction;
|
let exit_direction = other.direction;
|
||||||
|
// Note that unlike other tiles that care about whether they're wired, a blue teleporter
|
||||||
|
// considers itself part of a network if it contains any wires at all, regardless of
|
||||||
|
// whether they connect to anything
|
||||||
if (! me.wire_directions) {
|
if (! me.wire_directions) {
|
||||||
// TODO cc2 has a bug where, once it wraps around to the bottom right, it seems to
|
// TODO cc2 has a bug where, once it wraps around to the bottom right, it seems to
|
||||||
// forget that it was ever looking for an unwired teleport and will just grab the
|
// forget that it was ever looking for an unwired teleport and will just grab the
|
||||||
// first one it sees
|
// first one it sees
|
||||||
for (let dest of level.iter_tiles_in_reading_order(me.cell, 'teleport_blue', true)) {
|
for (let dest of level.iter_tiles_in_reading_order(me.cell, 'teleport_blue', true)) {
|
||||||
yield [dest, exit_direction];
|
if (! dest.wire_directions) {
|
||||||
|
yield [dest, exit_direction];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wired blue teleports form a network, which means we have to walk all wires from this
|
// Wired blue teleports form an isolated network, so we have to walk the circuit we're
|
||||||
// point, collect a list of all possible blue teleports, and then sort them so we can
|
// on, collect a list of all possible blue teleports, and then sort them so we can try
|
||||||
// try them in the right order.
|
// them in the right order.
|
||||||
// Complicating this somewhat, logic gates act as diodes: we can walk through a logic
|
// Complicating this somewhat, logic gates act as diodes: we can walk through a logic
|
||||||
// gate if we're connected to one of its inputs AND its output is enabled, but we can't
|
// gate if we're connected to one of its inputs AND its output is enabled, but we can't
|
||||||
// walk "backwards" through it.
|
// walk "backwards" through it.
|
||||||
@ -1185,66 +1187,45 @@ const TILE_TYPES = {
|
|||||||
// behavior is not and will never be emulated. No level in CC2 or even CC2LP1 uses blue
|
// behavior is not and will never be emulated. No level in CC2 or even CC2LP1 uses blue
|
||||||
// teleporters wired into logic gates, so even the ordering is not interesting imo.)
|
// teleporters wired into logic gates, so even the ordering is not interesting imo.)
|
||||||
// Anyway, let's do a breadth-first search for teleporters.
|
// Anyway, let's do a breadth-first search for teleporters.
|
||||||
let seeds = [me.cell];
|
let walked_circuits = new Set;
|
||||||
let found = [];
|
let candidate_teleporters = new Set;
|
||||||
let seen = new Set;
|
let circuits = me.circuits;
|
||||||
while (seeds.length > 0) {
|
for (let i = 0; i < circuits.length; i++) {
|
||||||
let next_seeds = [];
|
let circuit = circuits[i];
|
||||||
for (let cell of seeds) {
|
if (! circuit || walked_circuits.has(circuit))
|
||||||
if (seen.has(cell))
|
continue;
|
||||||
continue;
|
walked_circuits.add(circuit);
|
||||||
seen.add(cell);
|
|
||||||
|
|
||||||
let wired = cell.get_wired_tile();
|
for (let [tile, edges] of circuit.tiles.entries()) {
|
||||||
if (! wired || wired.wire_directions === 0)
|
if (tile.type === me.type) {
|
||||||
continue;
|
candidate_teleporters.add(tile);
|
||||||
|
}
|
||||||
// Check for a blue teleporter
|
else if (tile.type.name === 'logic_gate' && ! circuit.inputs.get(tile)) {
|
||||||
let dest;
|
// This logic gate is functioning as an output, so walk through it and also
|
||||||
for (let tile of cell) {
|
// trace any circuits that treat it as an input (as long as those circuits
|
||||||
if (tile.type.name === 'teleport_blue') {
|
// are currently powered)
|
||||||
found.push(tile);
|
for (let subcircuit of tile.circuits) {
|
||||||
break;
|
if (subcircuit && subcircuit.is_powered && subcircuit.inputs.get(tile)) {
|
||||||
|
circuits.push(subcircuit);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Search our neighbors
|
|
||||||
for (let direction of Object.keys(DIRECTIONS)) {
|
|
||||||
if (! (wired.wire_directions & DIRECTIONS[direction].bit))
|
|
||||||
continue;
|
|
||||||
let neighbor = level.get_neighboring_cell(cell, direction);
|
|
||||||
if (! neighbor || seen.has(neighbor))
|
|
||||||
continue;
|
|
||||||
let neighbor_wired = neighbor.get_wired_tile();
|
|
||||||
if (! neighbor_wired)
|
|
||||||
continue;
|
|
||||||
if (! (neighbor_wired.wire_directions & DIRECTIONS[DIRECTIONS[direction].opposite].bit))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
// TODO check for logic gate
|
|
||||||
// TODO need to know the direction we came in so we can get the right ones
|
|
||||||
// going out!
|
|
||||||
// FIXME this needs to understand crossings, and both this and basic wiring
|
|
||||||
// need to understand how blue/red teleports convey current
|
|
||||||
|
|
||||||
next_seeds.push(neighbor);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
seeds = next_seeds;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now that we have a list of candidate exits, sort it in reverse reading order,
|
// Now that we have a set of candidate destinations, sort it in reverse reading order,
|
||||||
// starting from ourselves. Easiest way to do this is to make a map of cell indices,
|
// starting from ourselves. Easiest way to do this is to make a map of cell indices,
|
||||||
// shifted so that we're at zero, then sort in reverse
|
// shifted so that we're at zero, then sort in reverse
|
||||||
let dest_indices = new Map;
|
let dest_indices = new Map;
|
||||||
let our_index = me.cell.x + me.cell.y * level.size_x;
|
let our_index = me.cell.x + me.cell.y * level.size_x;
|
||||||
let level_size = level.size_x * level.size_y;
|
let level_size = level.size_x * level.size_y;
|
||||||
for (let dest of found) {
|
for (let dest of candidate_teleporters) {
|
||||||
dest_indices.set(dest, (
|
dest_indices.set(dest, (
|
||||||
(dest.cell.x + dest.cell.y * level.size_x)
|
(dest.cell.x + dest.cell.y * level.size_x)
|
||||||
- our_index + level_size
|
- our_index + level_size
|
||||||
) % level_size);
|
) % level_size);
|
||||||
}
|
}
|
||||||
|
let found = Array.from(candidate_teleporters);
|
||||||
found.sort((a, b) => dest_indices.get(b) - dest_indices.get(a));
|
found.sort((a, b) => dest_indices.get(b) - dest_indices.get(a));
|
||||||
for (let dest of found) {
|
for (let dest of found) {
|
||||||
yield [dest, exit_direction];
|
yield [dest, exit_direction];
|
||||||
@ -1255,8 +1236,11 @@ const TILE_TYPES = {
|
|||||||
draw_layer: DRAW_LAYERS.terrain,
|
draw_layer: DRAW_LAYERS.terrain,
|
||||||
wire_propagation_mode: 'none',
|
wire_propagation_mode: 'none',
|
||||||
teleport_allow_override: true,
|
teleport_allow_override: true,
|
||||||
_is_active(me) {
|
_is_active(me, level) {
|
||||||
return ! (me.wire_directions && (me.cell.powered_edges & me.wire_directions) === 0);
|
// FIXME must be connected to something that can convey current: a wire, a switch, a
|
||||||
|
// blue teleporter, etc; NOT nothing, a wall, a transmogrifier, a force floor, etc.
|
||||||
|
// this is also how blue teleporters, transmogrifiers, and railroads work!
|
||||||
|
return me.powered_edges || ! level.is_tile_wired(me);
|
||||||
},
|
},
|
||||||
*teleport_dest_order(me, level, other) {
|
*teleport_dest_order(me, level, other) {
|
||||||
// Wired red teleporters can be turned off, which disconnects them from every other red
|
// Wired red teleporters can be turned off, which disconnects them from every other red
|
||||||
@ -1264,9 +1248,8 @@ const TILE_TYPES = {
|
|||||||
// A red teleporter is considered wired only if it has wires itself. However, CC2 also
|
// A red teleporter is considered wired only if it has wires itself. However, CC2 also
|
||||||
// has the bizarre behavior of NOT considering a red teleporter wired if none of its
|
// has the bizarre behavior of NOT considering a red teleporter wired if none of its
|
||||||
// wires are directly connected to another neighboring wire.
|
// wires are directly connected to another neighboring wire.
|
||||||
// FIXME implement that, merge current code with is_cell_wired
|
|
||||||
let iterable;
|
let iterable;
|
||||||
if (this._is_active(me)) {
|
if (this._is_active(me, level)) {
|
||||||
iterable = level.iter_tiles_in_reading_order(me.cell, 'teleport_red');
|
iterable = level.iter_tiles_in_reading_order(me.cell, 'teleport_red');
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@ -1274,7 +1257,7 @@ const TILE_TYPES = {
|
|||||||
}
|
}
|
||||||
let exit_direction = other.direction;
|
let exit_direction = other.direction;
|
||||||
for (let tile of iterable) {
|
for (let tile of iterable) {
|
||||||
if (tile === me || this._is_active(tile)) {
|
if (tile === me || this._is_active(tile, level)) {
|
||||||
// Red teleporters allow exiting in any direction, searching clockwise
|
// Red teleporters allow exiting in any direction, searching clockwise
|
||||||
yield [tile, exit_direction];
|
yield [tile, exit_direction];
|
||||||
yield [tile, DIRECTIONS[exit_direction].right];
|
yield [tile, DIRECTIONS[exit_direction].right];
|
||||||
@ -1283,7 +1266,7 @@ const TILE_TYPES = {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// TODO inactive ones don't animate
|
// TODO inactive ones don't animate; transmogrifiers too
|
||||||
/*
|
/*
|
||||||
visual_state(me) {
|
visual_state(me) {
|
||||||
return this._is_active(me) ? 'active' : 'inactive';
|
return this._is_active(me) ? 'active' : 'inactive';
|
||||||
@ -1615,10 +1598,10 @@ const TILE_TYPES = {
|
|||||||
let cxn = me.gate_def[i];
|
let cxn = me.gate_def[i];
|
||||||
let dirinfo = DIRECTIONS[dir];
|
let dirinfo = DIRECTIONS[dir];
|
||||||
if (cxn === 'in0') {
|
if (cxn === 'in0') {
|
||||||
input0 = (me.cell.powered_edges & dirinfo.bit) !== 0;
|
input0 = (me.powered_edges & dirinfo.bit) !== 0;
|
||||||
}
|
}
|
||||||
else if (cxn === 'in1') {
|
else if (cxn === 'in1') {
|
||||||
input1 = (me.cell.powered_edges & dirinfo.bit) !== 0;
|
input1 = (me.powered_edges & dirinfo.bit) !== 0;
|
||||||
}
|
}
|
||||||
else if (cxn === 'out0') {
|
else if (cxn === 'out0') {
|
||||||
outbit0 = dirinfo.bit;
|
outbit0 = dirinfo.bit;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user