Introduce a DrawPacket to consolidate draw arguments; fix blurriness of double-size monsters

This commit is contained in:
Eevee (Evelyn Woods) 2021-01-08 16:28:08 -07:00
parent c60158cc47
commit 8f40f575bf
2 changed files with 158 additions and 117 deletions

View File

@ -1,7 +1,36 @@
import { DIRECTIONS, LAYERS } from './defs.js'; import { DIRECTIONS, LAYERS } from './defs.js';
import { mk } from './util.js'; import { mk } from './util.js';
import { DrawPacket } from './tileset.js';
import TILE_TYPES from './tiletypes.js'; import TILE_TYPES from './tiletypes.js';
class CanvasRendererDrawPacket extends DrawPacket {
constructor(renderer, ctx, tic, perception) {
super(tic, perception);
this.renderer = renderer;
this.ctx = ctx;
// Canvas position of the cell being drawn
this.x = 0;
this.y = 0;
// Offset within the cell, for actors in motion
this.offsetx = 0;
this.offsety = 0;
}
blit(tx, ty, mx = 0, my = 0, mw = 1, mh = mw, mdx = mx, mdy = my) {
this.renderer.blit(this.ctx,
tx + mx, ty + my,
this.x + this.offsetx + mdx, this.y + this.offsety + mdy,
mw, mh);
}
blit_aligned(tx, ty, mx = 0, my = 0, mw = 1, mh = mw, mdx = mx, mdy = my) {
this.renderer.blit(this.ctx,
tx + mx, ty + my,
this.x + mdx, this.y + mdy,
mw, mh);
}
}
export class CanvasRenderer { export class CanvasRenderer {
constructor(tileset, fixed_size = null) { constructor(tileset, fixed_size = null) {
this.tileset = tileset; this.tileset = tileset;
@ -92,18 +121,6 @@ export class CanvasRenderer {
dx * tw, dy * th, w * tw, h * th); dx * tw, dy * th, w * tw, h * th);
} }
_make_tileset_blitter(ctx, offsetx = 0, offsety = 0) {
// The blit we pass to the tileset has a different signature than our own:
// blit(
// source_tile_x, source_tile_y,
// mask_x = 0, mask_y = 0, mask_width = 1, mask_height = mask_width,
// mask_dx = mask_x, mask_dy = mask_y)
// This makes it easier to use in the extremely common case of drawing part of one tile atop
// another tile, but still aligned to the grid.
return (tx, ty, mx = 0, my = 0, mw = 1, mh = mw, mdx = mx, mdy = my) =>
this.blit(ctx, tx + mx, ty + my, offsetx + mdx, offsety + mdy, mw, mh);
}
_adjust_viewport_if_dirty() { _adjust_viewport_if_dirty() {
if (! this.viewport_dirty) if (! this.viewport_dirty)
return; return;
@ -171,6 +188,7 @@ export class CanvasRenderer {
// Tiles in motion (i.e., actors) don't want to be overdrawn by neighboring tiles' terrain, // Tiles in motion (i.e., actors) don't want to be overdrawn by neighboring tiles' terrain,
// so draw in three passes: everything below actors, actors, and everything above actors // so draw in three passes: everything below actors, actors, and everything above actors
// neighboring terrain // neighboring terrain
let packet = new CanvasRendererDrawPacket(this, this.ctx, tic, this.perception);
for (let x = xf0; x <= x1; x++) { for (let x = xf0; x <= x1; x++) {
for (let y = yf0; y <= y1; y++) { for (let y = yf0; y <= y1; y++) {
let cell = this.level.cell(x, y); let cell = this.level.cell(x, y);
@ -179,9 +197,9 @@ export class CanvasRenderer {
if (! tile) if (! tile)
continue; continue;
this.tileset.draw( packet.x = x - x0;
tile, tic, this.perception, packet.y = y - y0;
this._make_tileset_blitter(this.ctx, x - x0, y - y0)); this.tileset.draw(tile, packet);
} }
} }
} }
@ -199,24 +217,30 @@ export class CanvasRenderer {
vy = Math.floor(vy * th + 0.5) / th; vy = Math.floor(vy * th + 0.5) / th;
// For blocks, perception only applies if there's something of interest underneath // For blocks, perception only applies if there's something of interest underneath
let perception = this.perception; if (this.perception !== 'normal' && actor.type.is_block &&
if (perception !== 'normal' && actor.type.is_block &&
! cell.some(t => t && t.type.layer < LAYERS.actor && ! ( ! cell.some(t => t && t.type.layer < LAYERS.actor && ! (
t.type.name === 'floor' && (t.wire_directions | t.wire_tunnel_directions) === 0))) t.type.name === 'floor' && (t.wire_directions | t.wire_tunnel_directions) === 0)))
{ {
perception = 'normal'; packet.perception = 'normal';
}
else {
packet.perception = this.perception;
} }
let blit = this._make_tileset_blitter(this.ctx, vx - x0, vy - y0); packet.x = x - x0;
packet.y = y - y0;
packet.offsetx = vx - x;
packet.offsety = vy - y;
// Draw the active player background // Draw the active player background
if (actor === this.active_player) { if (actor === this.active_player) {
this.tileset.draw_type('#active-player-background', null, tic, perception, blit); this.tileset.draw_type('#active-player-background', null, packet);
} }
this.tileset.draw(actor, tic, perception, blit); this.tileset.draw(actor, packet);
} }
} }
packet.perception = this.perception;
for (let x = xf0; x <= x1; x++) { for (let x = xf0; x <= x1; x++) {
for (let y = yf0; y <= y1; y++) { for (let y = yf0; y <= y1; y++) {
let cell = this.level.cell(x, y); let cell = this.level.cell(x, y);
@ -225,9 +249,7 @@ export class CanvasRenderer {
if (! tile) if (! tile)
continue; continue;
this.tileset.draw( this.tileset.draw(tile, packet);
tile, tic, this.perception,
this._make_tileset_blitter(this.ctx, x - x0, y - y0));
} }
} }
} }
@ -270,6 +292,7 @@ export class CanvasRenderer {
draw_static_region(x0, y0, x1, y1, destx = x0, desty = y0) { draw_static_region(x0, y0, x1, y1, destx = x0, desty = y0) {
this._adjust_viewport_if_dirty(); this._adjust_viewport_if_dirty();
let packet = new CanvasRendererDrawPacket(this, this.ctx, 0.0, this.perception);
for (let x = x0; x <= x1; x++) { for (let x = x0; x <= x1; x++) {
for (let y = y0; y <= y1; y++) { for (let y = y0; y <= y1; y++) {
let cell = this.level.cell(x, y); let cell = this.level.cell(x, y);
@ -283,9 +306,11 @@ export class CanvasRenderer {
// For actors (i.e., blocks), perception only applies if there's something // For actors (i.e., blocks), perception only applies if there's something
// of potential interest underneath // of potential interest underneath
let perception = this.perception; if (this.perception !== 'normal' && tile.type.is_block && ! seen_anything_interesting) {
if (perception !== 'normal' && tile.type.is_actor && ! seen_anything_interesting) { packet.perception = 'normal';
perception = 'normal'; }
else {
packet.perception = this.perception;
} }
if (tile.type.layer < LAYERS.actor && ! ( if (tile.type.layer < LAYERS.actor && ! (
@ -294,9 +319,9 @@ export class CanvasRenderer {
seen_anything_interesting = true; seen_anything_interesting = true;
} }
this.tileset.draw( packet.x = destx + x - x0;
tile, 0, perception, packet.y = desty + y - y0;
this._make_tileset_blitter(this.ctx, destx + x - x0, desty + y - y0)); this.tileset.draw(tile, packet);
} }
} }
} }
@ -305,8 +330,10 @@ export class CanvasRenderer {
create_tile_type_canvas(name, tile = null) { create_tile_type_canvas(name, tile = null) {
let canvas = mk('canvas', {width: this.tileset.size_x, height: this.tileset.size_y}); let canvas = mk('canvas', {width: this.tileset.size_x, height: this.tileset.size_y});
let ctx = canvas.getContext('2d'); let ctx = canvas.getContext('2d');
// Individual tile types always reveal what they are // Individual tile types always reveal what they are
this.tileset.draw_type(name, tile, 0, 'palette', this._make_tileset_blitter(ctx)); let packet = new CanvasRendererDrawPacket(this, ctx, 0.0, 'palette');
this.tileset.draw_type(name, tile, packet);
return canvas; return canvas;
} }
} }

View File

@ -948,6 +948,26 @@ export const TILESET_LAYOUTS = {
lexy: LL_TILESET_LAYOUT, lexy: LL_TILESET_LAYOUT,
}; };
// Bundle of arguments for drawing a tile, containing some standard state about the game
export class DrawPacket {
constructor(tic = 0, perception = 'normal') {
this.tic = tic;
this.perception = 'normal';
}
// Draw a tile (or region) from the tileset. The caller is presumed to know where the tile
// goes, so the arguments here are only about how to find the tile on the sheet.
// tx, ty: Tile coordinates (from the tileset, measured in cells)
// mx, my, mw, mh: Optional mask to use for drawing a subset of a tile (or occasionally tiles)
// mdx, mdy: Where to draw the masked part; defaults to drawing aligned with the tile
blit(tx, ty, mx = 0, my = 0, mw = 1, mh = mw, mdx = mx, mdy = my) {}
// Same, but do not interpolate the position of an actor in motion; always draw it exactly in
// the cell it claims to be in
blit_aligned(tx, ty, mx = 0, my = 0, mw = 1, mh = mw, mdx = mx, mdy = my) {}
}
export class Tileset { export class Tileset {
constructor(image, layout, size_x, size_y) { constructor(image, layout, size_x, size_y) {
// XXX curiously, i note that .image is never used within this class // XXX curiously, i note that .image is never used within this class
@ -958,15 +978,15 @@ export class Tileset {
this.animation_slowdown = 2; this.animation_slowdown = 2;
} }
draw(tile, tic, perception, blit) { draw(tile, packet) {
this.draw_type(tile.type.name, tile, tic, perception, blit); this.draw_type(tile.type.name, tile, packet);
} }
// Draw a "standard" drawspec, which is either: // Draw a "standard" drawspec, which is either:
// - a single tile: [x, y] // - a single tile: [x, y]
// - an animation: [[x0, y0], [x1, y1], ...] // - an animation: [[x0, y0], [x1, y1], ...]
// - a directional tile: { north: T, east: T, ... } where T is either of the above // - a directional tile: { north: T, east: T, ... } where T is either of the above
_draw_standard(drawspec, tile, tic, blit, mask = []) { _draw_standard(drawspec, tile, packet) {
// If we have an object, it must be a table of directions // If we have an object, it must be a table of directions
let coords = drawspec; let coords = drawspec;
if (!(coords instanceof Array)) { if (!(coords instanceof Array)) {
@ -975,14 +995,11 @@ export class Tileset {
// Deal with animation // Deal with animation
if (coords[0] instanceof Array) { if (coords[0] instanceof Array) {
if (tic === null) { if (tile && tile.movement_speed) {
coords = coords[0];
}
else if (tile && tile.movement_speed) {
// This tile reports its own animation timing (in frames), so trust that, and use // This tile reports its own animation timing (in frames), so trust that, and use
// the current tic's fraction. If we're between tics, interpolate. // the current tic's fraction. If we're between tics, interpolate.
// FIXME if the game ever runs every frame we will have to adjust the interpolation // FIXME if the game ever runs every frame we will have to adjust the interpolation
let p = ((tile.movement_speed - tile.movement_cooldown) + tic % 1 * 3) / tile.movement_speed; let p = ((tile.movement_speed - tile.movement_cooldown) + packet.tic % 1 * 3) / tile.movement_speed;
if (this.animation_slowdown > 1 && ! tile.type.ttl) { if (this.animation_slowdown > 1 && ! tile.type.ttl) {
// The players have full walk animations, but they look very silly when squeezed // The players have full walk animations, but they look very silly when squeezed
// into the span of a single step, so instead we only play half at a time. The // into the span of a single step, so instead we only play half at a time. The
@ -993,7 +1010,7 @@ export class Tileset {
// offset us into which half to play, then divide by 2 to renormalize. // offset us into which half to play, then divide by 2 to renormalize.
// Which half to use is determined by when the animation /started/, as measured // Which half to use is determined by when the animation /started/, as measured
// in animation lengths. // in animation lengths.
let start_time = (tic * 3 / tile.movement_speed) - p; let start_time = (packet.tic * 3 / tile.movement_speed) - p;
// Rounding smooths out float error (assuming the framerate never exceeds 1000) // Rounding smooths out float error (assuming the framerate never exceeds 1000)
let segment = Math.floor(Math.round(start_time * 1000) / 1000 % this.animation_slowdown); let segment = Math.floor(Math.round(start_time * 1000) / 1000 % this.animation_slowdown);
p = (p + segment) / this.animation_slowdown; p = (p + segment) / this.animation_slowdown;
@ -1012,21 +1029,21 @@ export class Tileset {
} }
else { else {
// This tile animates on a global timer, one cycle every quarter of a second // This tile animates on a global timer, one cycle every quarter of a second
coords = coords[Math.floor(tic / this.animation_slowdown % 5 / 5 * coords.length)]; coords = coords[Math.floor(packet.tic / this.animation_slowdown % 5 / 5 * coords.length)];
} }
} }
blit(coords[0], coords[1], ...mask); packet.blit(coords[0], coords[1]);
} }
_draw_letter(drawspec, tile, tic, blit) { _draw_letter(drawspec, tile, packet) {
this._draw_standard(drawspec.base, tile, tic, blit); this._draw_standard(drawspec.base, tile, packet);
let glyph = tile ? tile.overlaid_glyph : "?"; let glyph = tile ? tile.overlaid_glyph : "?";
if (drawspec.letter_glyphs[glyph]) { if (drawspec.letter_glyphs[glyph]) {
let [x, y] = drawspec.letter_glyphs[glyph]; let [x, y] = drawspec.letter_glyphs[glyph];
// XXX size is hardcoded here, but not below, meh // XXX size is hardcoded here, but not below, meh
blit(x, y, 0, 0, 0.5, 0.5, 0.25, 0.25); packet.blit(x, y, 0, 0, 0.5, 0.5, 0.25, 0.25);
} }
else { else {
// Look for a range // Look for a range
@ -1036,7 +1053,7 @@ export class Tileset {
let t = u - rangedef.range[0]; let t = u - rangedef.range[0];
let x = rangedef.x0 + rangedef.w * (t % rangedef.columns); let x = rangedef.x0 + rangedef.w * (t % rangedef.columns);
let y = rangedef.y0 + rangedef.h * Math.floor(t / rangedef.columns); let y = rangedef.y0 + rangedef.h * Math.floor(t / rangedef.columns);
blit(x, y, 0, 0, rangedef.w, rangedef.h, packet.blit(x, y, 0, 0, rangedef.w, rangedef.h,
(1 - rangedef.w) / 2, (1 - rangedef.h) / 2); (1 - rangedef.w) / 2, (1 - rangedef.h) / 2);
break; break;
} }
@ -1044,65 +1061,65 @@ export class Tileset {
} }
} }
_draw_thin_walls(drawspec, tile, tic, blit) { _draw_thin_walls(drawspec, tile, packet) {
let edges = tile ? tile.edges : 0x0f; let edges = tile ? tile.edges : 0x0f;
// TODO it would be /extremely/ cool to join corners diagonally, but i can't do that without // TODO it would be /extremely/ cool to join corners diagonally, but i can't do that without
// access to the context, which defeats the whole purpose of this scheme. damn // access to the context, which defeats the whole purpose of this scheme. damn
if (edges & DIRECTIONS['north'].bit) { if (edges & DIRECTIONS['north'].bit) {
blit(...drawspec.thin_walls_ns, 0, 0, 1, 0.5); packet.blit(...drawspec.thin_walls_ns, 0, 0, 1, 0.5);
} }
if (edges & DIRECTIONS['east'].bit) { if (edges & DIRECTIONS['east'].bit) {
blit(...drawspec.thin_walls_ew, 0.5, 0, 0.5, 1); packet.blit(...drawspec.thin_walls_ew, 0.5, 0, 0.5, 1);
} }
if (edges & DIRECTIONS['south'].bit) { if (edges & DIRECTIONS['south'].bit) {
blit(...drawspec.thin_walls_ns, 0, 0.5, 1, 0.5); packet.blit(...drawspec.thin_walls_ns, 0, 0.5, 1, 0.5);
} }
if (edges & DIRECTIONS['west'].bit) { if (edges & DIRECTIONS['west'].bit) {
blit(...drawspec.thin_walls_ew, 0, 0, 0.5, 1); packet.blit(...drawspec.thin_walls_ew, 0, 0, 0.5, 1);
} }
} }
_draw_thin_walls_cc1(drawspec, tile, tic, blit) { _draw_thin_walls_cc1(drawspec, tile, packet) {
let edges = tile ? tile.edges : 0x0f; let edges = tile ? tile.edges : 0x0f;
// This is kinda best-effort since the tiles are opaque and not designed to combine // This is kinda best-effort since the tiles are opaque and not designed to combine
if (edges === (DIRECTIONS['south'].bit | DIRECTIONS['east'].bit)) { if (edges === (DIRECTIONS['south'].bit | DIRECTIONS['east'].bit)) {
blit(...drawspec.southeast); packet.blit(...drawspec.southeast);
} }
else if (edges & DIRECTIONS['north'].bit) { else if (edges & DIRECTIONS['north'].bit) {
blit(...drawspec.north); packet.blit(...drawspec.north);
} }
else if (edges & DIRECTIONS['east'].bit) { else if (edges & DIRECTIONS['east'].bit) {
blit(...drawspec.east); packet.blit(...drawspec.east);
} }
else if (edges & DIRECTIONS['south'].bit) { else if (edges & DIRECTIONS['south'].bit) {
blit(...drawspec.south); packet.blit(...drawspec.south);
} }
else { else {
blit(...drawspec.west); packet.blit(...drawspec.west);
} }
} }
_draw_bomb_fuse(drawspec, tile, tic, blit) { _draw_bomb_fuse(drawspec, tile, packet) {
// Draw the base bomb // Draw the base bomb
this._draw_standard(drawspec.bomb, tile, tic, blit); this._draw_standard(drawspec.bomb, tile, packet);
// The fuse is made up of four quarter-tiles and animates... um... at a rate. I cannot // The fuse is made up of four quarter-tiles and animates... um... at a rate. I cannot
// tell. I have spent over an hour poring over this and cannot find a consistent pattern. // tell. I have spent over an hour poring over this and cannot find a consistent pattern.
// It might be random! I'm gonna say it loops every 0.3 seconds = 18 frames, so 4.5 frames // It might be random! I'm gonna say it loops every 0.3 seconds = 18 frames, so 4.5 frames
// per cel, I guess. No one will know. (But... I'll know.) // per cel, I guess. No one will know. (But... I'll know.)
// Also it's drawn in the upper right, that's important. // Also it's drawn in the upper right, that's important.
let cel = Math.floor(tic / 0.3 * 4) % 4; let cel = Math.floor(packet.tic / 0.3 * 4) % 4;
blit(...drawspec.fuse, 0.5 * (cel % 2), 0.5 * Math.floor(cel / 2), 0.5, 0.5, 0.5, 0); packet.blit(...drawspec.fuse, 0.5 * (cel % 2), 0.5 * Math.floor(cel / 2), 0.5, 0.5, 0.5, 0);
} }
_draw_double_size_monster(drawspec, tile, tic, blit) { _draw_double_size_monster(drawspec, tile, packet) {
// CC2's tileset has double-size art for blobs and walkers that spans the tile they're // CC2's tileset has double-size art for blobs and walkers that spans the tile they're
// moving from AND the tile they're moving into. // moving from AND the tile they're moving into.
// First, of course, this only happens if they're moving at all. // First, of course, this only happens if they're moving at all.
if (! tile || ! tile.movement_speed) { if (! tile || ! tile.movement_speed) {
this._draw_standard(drawspec.base, tile, tic, blit); this._draw_standard(drawspec.base, tile, packet);
return; return;
} }
@ -1118,6 +1135,7 @@ export class Tileset {
else if (tile.direction === 'south') { else if (tile.direction === 'south') {
axis_cels = drawspec.vertical; axis_cels = drawspec.vertical;
h = 2; h = 2;
y = -1;
} }
else if (tile.direction === 'west') { else if (tile.direction === 'west') {
axis_cels = drawspec.horizontal; axis_cels = drawspec.horizontal;
@ -1127,32 +1145,28 @@ export class Tileset {
else if (tile.direction === 'east') { else if (tile.direction === 'east') {
axis_cels = drawspec.horizontal; axis_cels = drawspec.horizontal;
w = 2; w = 2;
x = -1;
} }
// FIXME lexy is n to 1, cc2 n-1 to 0, and this mixes them // FIXME lexy is n to 1, cc2 n-1 to 0, and this mixes them
let p = tile.movement_speed - tile.movement_cooldown; let p = tile.movement_speed - tile.movement_cooldown;
p = (p + tic % 1 * 3) / tile.movement_speed; p = (p + packet.tic % 1 * 3) / tile.movement_speed;
p = Math.min(p, 0.999); // FIXME hack for differing movement counters p = Math.min(p, 0.999); // FIXME hack for differing movement counters
let index = Math.floor(p * (axis_cels.length + 1)); let index = Math.floor(p * (axis_cels.length + 1));
if (index === 0 || index > axis_cels.length) { if (index === 0 || index > axis_cels.length) {
this._draw_standard(drawspec.base, tile, tic, blit); this._draw_standard(drawspec.base, tile, packet);
} }
else { else {
// Tragically we have to counter the renderer's attempts to place the tile at its visual
// position
// FIXME this gets off the pixel grid because the value already baked into blit() has
// already been rounded. i don't know how to fix this
let [vx, vy] = tile.visual_position(tic % 1);
let cel = reverse ? axis_cels[axis_cels.length - index] : axis_cels[index - 1]; let cel = reverse ? axis_cels[axis_cels.length - index] : axis_cels[index - 1];
blit(...cel, 0, 0, w, h, x - vx % 1, y - vy % 1); packet.blit_aligned(...cel, 0, 0, w, h, x, y);
} }
} }
_draw_rover(drawspec, tile, tic, blit) { _draw_rover(drawspec, tile, packet) {
// Rovers draw fairly normally (with their visual_state giving the monster they're copying), // Rovers draw fairly normally (with their visual_state giving the monster they're copying),
// but they also have an overlay indicating their direction // but they also have an overlay indicating their direction
let state = tile ? tile.type.visual_state(tile) : 'inert'; let state = tile ? tile.type.visual_state(tile) : 'inert';
this._draw_standard(drawspec[state], tile, tic, blit); this._draw_standard(drawspec[state], tile, packet);
if (! tile) if (! tile)
return; return;
@ -1163,68 +1177,68 @@ export class Tileset {
let index = {north: 0, east: 1, west: 2, south: 3}[tile.direction]; let index = {north: 0, east: 1, west: 2, south: 3}[tile.direction];
if (index === undefined) if (index === undefined)
return; return;
blit( packet.blit(
...drawspec.direction, ...drawspec.direction,
0.5 * (index % 2), 0.5 * Math.floor(index / 2), 0.5, 0.5, 0.5 * (index % 2), 0.5 * Math.floor(index / 2), 0.5, 0.5,
overlay_position[0], overlay_position[1]); overlay_position[0], overlay_position[1]);
} }
_draw_logic_gate(drawspec, tile, tic, blit) { _draw_logic_gate(drawspec, tile, packet) {
// Layer 1: wiring state // Layer 1: wiring state
// Always draw the unpowered wire base // Always draw the unpowered wire base
let unpowered_coords = this.layout['#unpowered']; let unpowered_coords = this.layout['#unpowered'];
let powered_coords = this.layout['#powered']; let powered_coords = this.layout['#powered'];
blit(...unpowered_coords); packet.blit(...unpowered_coords);
if (tile && tile.cell) { if (tile && tile.cell) {
// What goes on top varies a bit... // What goes on top varies a bit...
let r = this.layout['#wire-width'] / 2; let r = this.layout['#wire-width'] / 2;
if (tile.gate_type === 'not' || tile.gate_type === 'counter') { if (tile.gate_type === 'not' || tile.gate_type === 'counter') {
this._draw_fourway_tile_power(tile, 0x0f, blit); this._draw_fourway_tile_power(tile, 0x0f, packet);
} }
else { else {
if (tile.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); packet.blit(powered_coords[0], powered_coords[1], x0, y0, x1 - x0, y1 - y0);
} }
if (tile.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); packet.blit(powered_coords[0], powered_coords[1], x0, y0, x1 - x0, y1 - y0);
} }
if (tile.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);
blit(powered_coords[0], powered_coords[1], x0, y0, x1 - x0, y1 - y0); packet.blit(powered_coords[0], powered_coords[1], x0, y0, x1 - x0, y1 - y0);
} }
} }
} }
// Layer 2: the tile itself // Layer 2: the tile itself
this._draw_standard(drawspec.logic_gate_tiles[tile.gate_type], tile, tic, blit); this._draw_standard(drawspec.logic_gate_tiles[tile.gate_type], tile, packet);
// Layer 3: counter number // Layer 3: counter number
if (tile.gate_type === 'counter') { if (tile.gate_type === 'counter') {
blit(0, 3, tile.memory * 0.75, 0, 0.75, 1, 0.125, 0); packet.blit(0, 3, tile.memory * 0.75, 0, 0.75, 1, 0.125, 0);
} }
} }
_draw_fourway_tile_power(tile, wires, blit) { _draw_fourway_tile_power(tile, wires, packet) {
// 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.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, packet);
if (! tile.cell || powered === 0) if (! tile.cell || powered === 0)
return; return;
} }
this._draw_fourway_power_underlay(this.layout['#powered'], powered, blit); this._draw_fourway_power_underlay(this.layout['#powered'], powered, packet);
} }
_draw_fourway_power_underlay(drawspec, bits, blit) { _draw_fourway_power_underlay(drawspec, bits, packet) {
// Draw the part as a single rectangle, initially just a small dot in the center, but // Draw the part as a single rectangle, initially just a small dot in the center, but
// extending out to any edge that has a bit present // extending out to any edge that has a bit present
let wire_radius = this.layout['#wire-width'] / 2; let wire_radius = this.layout['#wire-width'] / 2;
@ -1244,13 +1258,13 @@ export class Tileset {
if (bits & DIRECTIONS['west'].bit) { if (bits & DIRECTIONS['west'].bit) {
x0 = 0; x0 = 0;
} }
blit(drawspec[0], drawspec[1], x0, y0, x1 - x0, y1 - y0); packet.blit(drawspec[0], drawspec[1], x0, y0, x1 - x0, y1 - y0);
} }
_draw_railroad(drawspec, tile, tic, blit) { _draw_railroad(drawspec, tile, packet) {
// All railroads have regular gravel underneath // All railroads have regular gravel underneath
// TODO would be nice to disambiguate since it's possible to have nothing visible // TODO would be nice to disambiguate since it's possible to have nothing visible
this._draw_standard(this.layout['gravel'], tile, tic, blit); this._draw_standard(this.layout['gravel'], tile, packet);
// FIXME what do i draw if there's no tile? // FIXME what do i draw if there's no tile?
let part_order = ['ne', 'se', 'sw', 'nw', 'ew', 'ns']; let part_order = ['ne', 'se', 'sw', 'nw', 'ew', 'ns'];
@ -1267,26 +1281,26 @@ export class Tileset {
let has_switch = (tile && tile.track_switch !== null); let has_switch = (tile && tile.track_switch !== null);
for (let part of visible_parts) { for (let part of visible_parts) {
this._draw_standard(drawspec.railroad_ties[part], tile, tic, blit); this._draw_standard(drawspec.railroad_ties[part], tile, packet);
} }
let tracks = has_switch ? drawspec.railroad_inactive : drawspec.railroad_active; let tracks = has_switch ? drawspec.railroad_inactive : drawspec.railroad_active;
for (let part of visible_parts) { for (let part of visible_parts) {
if (part !== topmost_part) { if (part !== topmost_part) {
this._draw_standard(tracks[part], tile, tic, blit); this._draw_standard(tracks[part], tile, packet);
} }
} }
if (topmost_part) { if (topmost_part) {
this._draw_standard(drawspec.railroad_active[topmost_part], tile, tic, blit); this._draw_standard(drawspec.railroad_active[topmost_part], tile, packet);
} }
if (has_switch) { if (has_switch) {
this._draw_standard(drawspec.railroad_switch, tile, tic, blit); this._draw_standard(drawspec.railroad_switch, tile, packet);
} }
} }
// Draws a tile type, given by name. Passing in a tile is optional, but // Draws a tile type, given by name. Passing in a tile is optional, but
// without it you'll get defaults. // without it you'll get defaults.
draw_type(name, tile, tic, perception, blit) { draw_type(name, tile, packet) {
let drawspec = this.layout[name]; let drawspec = this.layout[name];
if (drawspec === null) { if (drawspec === null) {
// This is explicitly never drawn (used for extra visual-only frills that don't exist in // This is explicitly never drawn (used for extra visual-only frills that don't exist in
@ -1305,13 +1319,13 @@ export class Tileset {
// the overlay (either a type name or a regular draw spec). // the overlay (either a type name or a regular draw spec).
// TODO chance of infinite recursion here // TODO chance of infinite recursion here
if (typeof drawspec.base === 'string') { if (typeof drawspec.base === 'string') {
this.draw_type(drawspec.base, tile, tic, perception, blit); this.draw_type(drawspec.base, tile, packet);
} }
else { else {
this._draw_standard(drawspec.base, tile, tic, blit); this._draw_standard(drawspec.base, tile, packet);
} }
if (typeof drawspec.overlay === 'string') { if (typeof drawspec.overlay === 'string') {
this.draw_type(drawspec.overlay, tile, tic, perception, blit); this.draw_type(drawspec.overlay, tile, packet);
return; return;
} }
else { else {
@ -1322,31 +1336,31 @@ export class Tileset {
// TODO shift everything to use this style, this is ridiculous // TODO shift everything to use this style, this is ridiculous
if (drawspec.special) { if (drawspec.special) {
if (drawspec.special === 'letter') { if (drawspec.special === 'letter') {
this._draw_letter(drawspec, tile, tic, blit); this._draw_letter(drawspec, tile, packet);
return; return;
} }
else if (drawspec.special === 'thin_walls') { else if (drawspec.special === 'thin_walls') {
this._draw_thin_walls(drawspec, tile, tic, blit); this._draw_thin_walls(drawspec, tile, packet);
return; return;
} }
else if (drawspec.special === 'thin_walls_cc1') { else if (drawspec.special === 'thin_walls_cc1') {
this._draw_thin_walls_cc1(drawspec, tile, tic, blit); this._draw_thin_walls_cc1(drawspec, tile, packet);
return; return;
} }
else if (drawspec.special === 'bomb-fuse') { else if (drawspec.special === 'bomb-fuse') {
this._draw_bomb_fuse(drawspec, tile, tic, blit); this._draw_bomb_fuse(drawspec, tile, packet);
return; return;
} }
else if (drawspec.special === 'double-size-monster') { else if (drawspec.special === 'double-size-monster') {
this._draw_double_size_monster(drawspec, tile, tic, blit); this._draw_double_size_monster(drawspec, tile, packet);
return; return;
} }
else if (drawspec.special === 'rover') { else if (drawspec.special === 'rover') {
this._draw_rover(drawspec, tile, tic, blit); this._draw_rover(drawspec, tile, packet);
return; return;
} }
else if (drawspec.special === 'perception') { else if (drawspec.special === 'perception') {
if (drawspec.modes.has(perception)) { if (drawspec.modes.has(packet.perception)) {
drawspec = drawspec.revealed; drawspec = drawspec.revealed;
} }
else { else {
@ -1354,11 +1368,11 @@ export class Tileset {
} }
} }
else if (drawspec.special === 'logic-gate') { else if (drawspec.special === 'logic-gate') {
this._draw_logic_gate(drawspec, tile, tic, blit); this._draw_logic_gate(drawspec, tile, packet);
return; return;
} }
else if (drawspec.special === 'railroad') { else if (drawspec.special === 'railroad') {
this._draw_railroad(drawspec, tile, tic, blit); this._draw_railroad(drawspec, tile, packet);
return; return;
} }
} }
@ -1371,9 +1385,9 @@ export class Tileset {
// TODO circuit block in motion doesn't inherit cell's power // TODO circuit block in motion doesn't inherit cell's power
if (tile && tile.wire_directions) { if (tile && tile.wire_directions) {
// Draw the base tile // Draw the base tile
blit(drawspec.base[0], drawspec.base[1]); packet.blit(drawspec.base[0], drawspec.base[1]);
this._draw_fourway_tile_power(tile, tile.wire_directions, blit); this._draw_fourway_tile_power(tile, tile.wire_directions, packet);
// Then draw the wired tile on top of it all // Then draw the wired tile on top of it all
if (tile.wire_directions === 0x0f && drawspec.wired_cross) { if (tile.wire_directions === 0x0f && drawspec.wired_cross) {
@ -1392,7 +1406,7 @@ export class Tileset {
coords = drawspec.base; coords = drawspec.base;
} }
else { else {
blit(drawspec.base[0], drawspec.base[1]); packet.blit(drawspec.base[0], drawspec.base[1]);
coords = drawspec.wired; coords = drawspec.wired;
} }
} }
@ -1405,7 +1419,7 @@ export class Tileset {
// Force floors animate their... cutout, I guess? // Force floors animate their... cutout, I guess?
let [x, y] = drawspec.base; let [x, y] = drawspec.base;
let duration = 3 * this.animation_slowdown; let duration = 3 * this.animation_slowdown;
x += drawspec.animate_width * (tic % duration / duration); x += drawspec.animate_width * (packet.tic % duration / duration);
// Round to tile width // Round to tile width
x = Math.floor(x * this.size_x + 0.5) / this.size_x; x = Math.floor(x * this.size_x + 0.5) / this.size_x;
coords = [x, y]; coords = [x, y];
@ -1414,7 +1428,7 @@ export class Tileset {
// Same, but along the other axis // Same, but along the other axis
let [x, y] = drawspec.base; let [x, y] = drawspec.base;
let duration = 3 * this.animation_slowdown; let duration = 3 * this.animation_slowdown;
y += drawspec.animate_height * (tic % duration / duration); y += drawspec.animate_height * (packet.tic % duration / duration);
// Round to tile height // Round to tile height
y = Math.floor(y * this.size_y + 0.5) / this.size_y; y = Math.floor(y * this.size_y + 0.5) / this.size_y;
coords = [x, y]; coords = [x, y];
@ -1437,7 +1451,7 @@ export class Tileset {
} }
} }
this._draw_standard(coords, tile, tic, blit); this._draw_standard(coords, tile, packet);
// Wired tiles may also have tunnels, drawn on top of everything else // Wired tiles may also have tunnels, drawn on top of everything else
if (drawspec.wired && tile && tile.wire_tunnel_directions) { if (drawspec.wired && tile && tile.wire_tunnel_directions) {
@ -1446,19 +1460,19 @@ export class Tileset {
let tunnel_length = 12/32; let tunnel_length = 12/32;
let tunnel_offset = (1 - tunnel_width) / 2; let tunnel_offset = (1 - tunnel_width) / 2;
if (tile.wire_tunnel_directions & DIRECTIONS['north'].bit) { if (tile.wire_tunnel_directions & DIRECTIONS['north'].bit) {
blit(tunnel_coords[0], tunnel_coords[1], packet.blit(tunnel_coords[0], tunnel_coords[1],
tunnel_offset, 0, tunnel_width, tunnel_length); tunnel_offset, 0, tunnel_width, tunnel_length);
} }
if (tile.wire_tunnel_directions & DIRECTIONS['south'].bit) { if (tile.wire_tunnel_directions & DIRECTIONS['south'].bit) {
blit(tunnel_coords[0], tunnel_coords[1], packet.blit(tunnel_coords[0], tunnel_coords[1],
tunnel_offset, 1 - tunnel_length, tunnel_width, tunnel_length); tunnel_offset, 1 - tunnel_length, tunnel_width, tunnel_length);
} }
if (tile.wire_tunnel_directions & DIRECTIONS['west'].bit) { if (tile.wire_tunnel_directions & DIRECTIONS['west'].bit) {
blit(tunnel_coords[0], tunnel_coords[1], packet.blit(tunnel_coords[0], tunnel_coords[1],
0, tunnel_offset, tunnel_length, tunnel_width); 0, tunnel_offset, tunnel_length, tunnel_width);
} }
if (tile.wire_tunnel_directions & DIRECTIONS['east'].bit) { if (tile.wire_tunnel_directions & DIRECTIONS['east'].bit) {
blit(tunnel_coords[0], tunnel_coords[1], packet.blit(tunnel_coords[0], tunnel_coords[1],
1 - tunnel_length, tunnel_offset, tunnel_length, tunnel_width); 1 - tunnel_length, tunnel_offset, tunnel_length, tunnel_width);
} }
} }
@ -1480,7 +1494,7 @@ export class Tileset {
if (tile.arrows.has('west')) { if (tile.arrows.has('west')) {
x0 = 0; x0 = 0;
} }
blit(x, y, x0, y0, x1 - x0, y1 - y0); packet.blit(x, y, x0, y0, x1 - x0, y1 - y0);
} }
} }