From ac6e33bb6cb2a0ffd057123d856e1909014e8d05 Mon Sep 17 00:00:00 2001 From: "Eevee (Evelyn Woods)" Date: Wed, 25 Nov 2020 01:14:15 -0700 Subject: [PATCH] Simplify blitting; fix arrow blitting; impl light switch; load more items --- js/format-c2g.js | 9 ++--- js/game.js | 8 +++-- js/renderer-canvas.js | 27 +++++++++----- js/tileset.js | 82 +++++++++++++++++++++++++++++++------------ js/tiletypes.js | 35 ++++++++++++++++++ 5 files changed, 122 insertions(+), 39 deletions(-) diff --git a/js/format-c2g.js b/js/format-c2g.js index 793e980..fc8f68e 100644 --- a/js/format-c2g.js +++ b/js/format-c2g.js @@ -405,7 +405,7 @@ const TILE_ENCODING = { error: "Timid chomper is not yet implemented, sorry!", }, 0x58: { - // TODO??? unused in main levels -- name: 'doppelganger2', + // TODO??? unused in main levels -- name: '', has_next: true, extra_args: [arg_direction], error: "Explosion animation is not implemented, sorry!", @@ -465,7 +465,6 @@ const TILE_ENCODING = { 0x62: { name: 'lightning_bolt', has_next: true, - error: "The lightning bolt is not yet implemented, sorry!", }, 0x63: { name: 'tank_yellow', @@ -609,11 +608,11 @@ const TILE_ENCODING = { }, 0x88: { name: 'light_switch_off', - error: "The light switch is not yet implemented, sorry!", + modifier: modifier_wire, }, 0x89: { name: 'light_switch_on', - error: "The light switch is not yet implemented, sorry!", + modifier: modifier_wire, }, 0x8a: { name: 'thief_keys', @@ -637,12 +636,10 @@ const TILE_ENCODING = { 0x8f: { name: 'bribe', has_next: true, - error: "The bribe is not yet implemented, sorry!", }, 0x90: { name: 'speed_boots', has_next: true, - error: "The speed boots are not yet implemented, sorry!", }, 0x91: { name: 'hook', diff --git a/js/game.js b/js/game.js index 80638b3..6d726e6 100644 --- a/js/game.js +++ b/js/game.js @@ -1140,9 +1140,11 @@ export class Level { if (! actor.cell) continue; // Only count when they're on a tile, not in transit! + // FIXME this causes us to power mechanisms next to us, but we're only supposed to touch + // wire we're standing on? let emitting = actor.movement_cooldown === 0 && actor.has_item('lightning_bolt'); if (emitting) { - neighbors.push([actor.cell, emitting]); + neighbors.push([actor.cell, 0x0f]); } if (emitting !== actor.emitting_edges) { any_changed = true; @@ -1227,7 +1229,9 @@ export class Level { else { // No tunnel; this is easy neighbor = this.get_neighboring_cell(cell, direction); - neighbor_wire = neighbor.get_wired_tile(); + if (neighbor) { + neighbor_wire = neighbor.get_wired_tile(); + } } if (neighbor && (neighbor.powered_edges & opposite_bit) === 0 && diff --git a/js/renderer-canvas.js b/js/renderer-canvas.js index 1ee1b4b..8fbd507 100644 --- a/js/renderer-canvas.js +++ b/js/renderer-canvas.js @@ -1,4 +1,4 @@ -import { DIRECTIONS } from './defs.js'; +import { DIRECTIONS, DRAW_LAYERS } from './defs.js'; import { mk } from './util.js'; import TILE_TYPES from './tiletypes.js'; @@ -125,33 +125,44 @@ export class CanvasRenderer { let y1 = Math.min(this.level.size_y - 1, Math.ceil(y0 + this.viewport_size_y)); // Draw one layer at a time, so animated objects aren't overdrawn by // neighboring terrain - // XXX layer count hardcoded here // FIXME this is a bit inefficient when there are a lot of rarely-used layers; consider // instead drawing everything under actors, then actors, then everything above actors? - for (let layer = 0; layer < 5; layer++) { + for (let layer = 0; layer < DRAW_LAYERS.MAX; layer++) { for (let x = xf0; x <= x1; x++) { for (let y = yf0; y <= y1; y++) { for (let tile of this.level.cells[y][x]) { if (tile.type.draw_layer !== layer) continue; + let vx, vy; if (tile.type.is_actor && // FIXME kind of a hack for the editor, which uses bare tile objects tile.visual_position) { // Handle smooth scrolling - let [vx, vy] = tile.visual_position(tic_offset); + [vx, vy] = tile.visual_position(tic_offset); // Round this to the pixel grid too! vx = Math.floor(vx * tw + 0.5) / tw; vy = Math.floor(vy * th + 0.5) / th; - this.tileset.draw(tile, tic, (sx, sy, dx = 0, dy = 0, w = 1, h = w) => - this.blit(this.ctx, sx, sy, vx - x0 + dx, vy - y0 + dy, w, h)); } else { // Non-actors can't move - this.tileset.draw(tile, tic, (sx, sy, dx = 0, dy = 0, w = 1, h = w) => - this.blit(this.ctx, sx, sy, x - x0 + dx, y - y0 + dy, w, h)); + vx = x; + vy = y; } + + // Note that the blit we pass to the tileset has a different signature: + // 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. + this.tileset.draw(tile, tic, (tx, ty, mx = 0, my = 0, mw = 1, mh = mw, mdx = mx, mdy = my) => + this.blit(this.ctx, + tx + mx, ty + my, + vx - x0 + mdx, vy - y0 + mdy, + mw, mh)); } } } diff --git a/js/tileset.js b/js/tileset.js index a9b4ac4..f1ead22 100644 --- a/js/tileset.js +++ b/js/tileset.js @@ -3,16 +3,36 @@ import TILE_TYPES from './tiletypes.js'; // TODO really need to specify this format more concretely, whoof // XXX special kinds of drawing i know this has for a fact: -// - letter tiles draw from a block of half-tiles onto the center of the base -// - slime and walkers have double-size tiles when moving +// - letter tiles draw from a block (one of two blocks!) of half-tiles onto the center of the base // - force floors are cropped from a double-size tile -// - wired tiles are a whole thing +// - wired tiles are a whole thing (floor) // - thin walls are packed into just two tiles -// - rover has a half-tile overlay for its direction? -// - railroad tracks overlay a Lot // - directional blocks have arrows in an awkward layout, not 4x4 grid but actually positioned on the edges // - green and purple toggle walls use an overlay -// - turtles use an overlay +// - turtles use an overlay, seem to pick a tile at random every so often +// - animations are common, should maybe have configurable timing?? +// - custom floors and walls /should/ be consolidated into a single tile probably +// - thin walls should probably be consolidated? +// - traps have a state + +// special features i currently have +// - directions for actors, can be used anywhere +// - arrows: for directional blocks +// - mask: for thin walls (though the idea is useful in many more places) +// - wired: for wired tiles +// - overlay: for green/purple walls mostly, also some bogus cc1 tiles + +// things that are currently NOT handled +// - bomb is supposed to have a fuse +// - critters should only animate when moving +// - rover animation depends on behavior, also has a quarter-tile overlay for its direction +// - slime and walkers have double-size tiles when moving +// - logic gates draw over the stuff underneath them +// - railroad tracks overlay a Lot +// - canopy, at all +// - swivel's floor (eugh) +// - xray vision +// - editor vision export const CC2_TILESET_LAYOUT = { '#wire-width': 1/16, @@ -76,8 +96,8 @@ export const CC2_TILESET_LAYOUT = { green_chip: [9, 3], chip_extra: [10, 3], chip: [11, 3], - // bribe - // mercury boot + bribe: [12, 3], + speed_boots: [13, 3], // canopy, xray // TODO lit @@ -112,7 +132,7 @@ export const CC2_TILESET_LAYOUT = { cleats: [2, 6], suction_boots: [3, 6], hiking_boots: [4, 6], - // speed boots...? not boots though + lightning_bolt: [5, 6], // weird translucent spiral // weird translucent red button_blue: [8, 6], @@ -300,7 +320,14 @@ export const CC2_TILESET_LAYOUT = { force_floor_all: [[0, 21], [1, 21], [2, 21], [3, 21], [4, 21], [5, 21], [6, 21], [7, 21]], // latches - // switch + light_switch_off: { + base: [14, 21], + wired: [12, 21], + }, + light_switch_on: { + base: [14, 21], + wired: [13, 21], + }, thief_keys: [15, 21], player: { @@ -376,6 +403,12 @@ export const CC2_TILESET_LAYOUT = { logic_gate: { // TODO currently, 'wired' can't coexist with visual state etc... // TODO *long sigh* of course, logic gates have parts with independent current too + // for a north-facing gate with two inputs, we have: + // - north output: just a wire, doesn't include center. -r 0 w -r + // - west output: bottom left quadrant, except for the middle wire part, but including the + // horizontal wire. 0 -r -r +r + // - east output: same but includes middle wire. -r -r +r +r + // TODO check if they're flipped for latches facing the other way 'latch-ccw': { north: [8, 21], east: [9, 21], @@ -742,8 +775,8 @@ export class Tileset { tile.cell && tile.cell.powered_edges & DIRECTIONS['north'].bit ? '#powered' : '#unpowered']; let wire_coords_ew = this.layout[ tile.cell && tile.cell.powered_edges & DIRECTIONS['east'].bit ? '#powered' : '#unpowered']; - blit(wire_coords_ns[0] + wire_inset, wire_coords_ns[1], wire_inset, 0, wire_radius * 2, 1); - blit(wire_coords_ew[0], wire_coords_ew[1] + wire_inset, 0, wire_inset, 1, wire_radius * 2); + blit(wire_coords_ns[0], wire_coords_ns[1], wire_inset, 0, wire_radius * 2, 1); + blit(wire_coords_ew[0], wire_coords_ew[1], 0, wire_inset, 1, wire_radius * 2); // Draw the cross tile on top coords = drawspec.wired_cross ?? drawspec.wired; @@ -771,7 +804,7 @@ export class Tileset { x0 = 0; } let wire_coords = this.layout[tile.cell && tile.cell.powered_edges ? '#powered' : '#unpowered']; - blit(wire_coords[0] + x0, wire_coords[1] + y0, x0, y0, x1 - x0, y1 - y0); + blit(wire_coords[0], wire_coords[1], x0, y0, x1 - x0, y1 - y0); // Then draw the wired tile on top of it all coords = drawspec.wired; @@ -877,7 +910,7 @@ export class Tileset { // Continue on with masking coords = drawspec.tile; let [x0, y0, w, h] = drawspec.mask; - blit(coords[0] + x0, coords[1] + y0, x0, y0, w, h); + blit(coords[0], coords[1], x0, y0, w, h); } else { if (!coords) console.error(name, tile); @@ -891,38 +924,41 @@ export class Tileset { let tunnel_length = 12/32; let tunnel_offset = (1 - tunnel_width) / 2; if (tile.wire_tunnel_directions & DIRECTIONS['north'].bit) { - blit(tunnel_coords[0] + tunnel_offset, tunnel_coords[1], + blit(tunnel_coords[0], tunnel_coords[1], tunnel_offset, 0, tunnel_width, tunnel_length); } if (tile.wire_tunnel_directions & DIRECTIONS['south'].bit) { - blit(tunnel_coords[0] + tunnel_offset, tunnel_coords[1] + 1 - tunnel_length, + blit(tunnel_coords[0], tunnel_coords[1], tunnel_offset, 1 - tunnel_length, tunnel_width, tunnel_length); } if (tile.wire_tunnel_directions & DIRECTIONS['west'].bit) { - blit(tunnel_coords[0], tunnel_coords[1] + tunnel_offset, + blit(tunnel_coords[0], tunnel_coords[1], 0, tunnel_offset, tunnel_length, tunnel_width); } if (tile.wire_tunnel_directions & DIRECTIONS['east'].bit) { - blit(tunnel_coords[0] + 1 - tunnel_length, tunnel_coords[1] + tunnel_offset, + blit(tunnel_coords[0], tunnel_coords[1], 1 - tunnel_length, tunnel_offset, tunnel_length, tunnel_width); } } // Directional blocks have arrows drawn on top + // TODO does cc2 draw even if there are no arrows? if (drawspec.arrows && tile && tile.arrows) { let [x, y] = drawspec.arrows; + let x0 = 0.25, y0 = 0.25, x1 = 0.75, y1 = 0.75; if (tile.arrows.has('north')) { - blit(x, y, 0, 0, 1, 0.25); + y0 = 0; } if (tile.arrows.has('east')) { - blit(x + 0.75, y, 0.75, 0, 0.25, 1); + x1 = 1; } if (tile.arrows.has('south')) { - blit(x, y + 0.75, 0, 0.75, 1, 0.25); + y1 = 1; } if (tile.arrows.has('west')) { - blit(x, y, 0, 0, 0.25, 1); + x0 = 0; } + blit(x, y, x0, y0, x1 - x0, y1 - y0); } // Special behavior for special objects @@ -952,7 +988,7 @@ export class Tileset { sy = (letter_spec.y0 + Math.floor(n / w)) * scale; } let offset = (1 - scale) / 2; - blit(sx, sy, offset, offset, scale, scale); + blit(sx, sy, 0, 0, 0.5, 0.5, offset, offset); } } } diff --git a/js/tiletypes.js b/js/tiletypes.js index 9b136f8..d2ae20a 100644 --- a/js/tiletypes.js +++ b/js/tiletypes.js @@ -1157,6 +1157,27 @@ const TILE_TYPES = { return me.gate_type; }, }, + // Light switches, kinda like the pink/black buttons but persistent + light_switch_off: { + draw_layer: DRAW_LAYERS.terrain, + is_power_source: true, + get_emitting_edges(me, level) { + return 0; + }, + on_arrive(me, level, other) { + level.transmute_tile(me, 'light_switch_on'); + }, + }, + light_switch_on: { + draw_layer: DRAW_LAYERS.terrain, + is_power_source: true, + get_emitting_edges(me, level) { + return me.wire_directions; + }, + on_arrive(me, level, other) { + level.transmute_tile(me, 'light_switch_off'); + }, + }, // Time alteration stopwatch_bonus: { @@ -1453,6 +1474,20 @@ const TILE_TYPES = { is_tool: true, blocks_collision: COLLISION.block_cc1 | COLLISION.monster_solid, }, + speed_boots: { + // TODO not implemented + draw_layer: DRAW_LAYERS.item, + is_item: true, + is_tool: true, + blocks_collision: COLLISION.block_cc1 | COLLISION.monster_solid, + }, + bribe: { + // TODO not implemented + draw_layer: DRAW_LAYERS.item, + is_item: true, + is_tool: true, + blocks_collision: COLLISION.block_cc1 | COLLISION.monster_solid, + }, // Progression player: {