Add a bunch of minor rendering stuff

- Added the active player background

- Added bomb fuses (though LL doesn't use them)

- Added CC2-style double-size blob and walker (though LL doesn't use them)

- Added the rover's directional overlay

- Added custom push animations

- Added custom bouncing heart animations

- Added a puff when opening a door or socket, or revealing a fake floor

- Fixed the rover's animations being a bit mixed up

- Fixed player walk animations occasionally being glitchy

- Touched up the fake floor x-ray tile

- Touched up the canopy x-ray tile

- Touched up the purple ball's shadows

- Touched up the transmogrifier and transmogrify flash
This commit is contained in:
Eevee (Evelyn Woods) 2021-01-05 17:10:21 -07:00
parent 31a1049655
commit aed96c8e41
11 changed files with 248 additions and 106 deletions

View File

@ -3127,6 +3127,7 @@ export class Editor extends PrimaryView {
// Automatically redraw only what's changed // Automatically redraw only what's changed
_redraw_dirty() { _redraw_dirty() {
// TODO draw sparkle background under the starting player
if (this._dirty_rect) { if (this._dirty_rect) {
this.renderer.draw_static_region( this.renderer.draw_static_region(
this._dirty_rect.left, this._dirty_rect.top, this._dirty_rect.left, this._dirty_rect.top,

View File

@ -1352,6 +1352,8 @@ class Player extends PrimaryView {
} }
this.message_el.textContent = this.level.hint_shown ?? ""; this.message_el.textContent = this.level.hint_shown ?? "";
this.renderer.set_active_player(this.level.remaining_players > 1 ? this.level.player : null);
// Keys appear in a consistent order // Keys appear in a consistent order
for (let [key, nodes] of Object.entries(this.inventory_key_nodes)) { for (let [key, nodes] of Object.entries(this.inventory_key_nodes)) {
let count = this.level.player.keyring[key] ?? 0; let count = this.level.player.keyring[key] ?? 0;
@ -2627,6 +2629,7 @@ class PackTestDialog extends DialogOverlay {
height: Math.min(this.renderer.canvas.height, level.size_y * tileset.size_y), height: Math.min(this.renderer.canvas.height, level.size_y * tileset.size_y),
}); });
this.renderer.set_level(level); this.renderer.set_level(level);
this.renderer.set_active_player(level.player);
this.renderer.draw(); this.renderer.draw();
canvas.getContext('2d').drawImage( canvas.getContext('2d').drawImage(
this.renderer.canvas, 0, 0, this.renderer.canvas, 0, 0,

View File

@ -28,6 +28,7 @@ export class CanvasRenderer {
this.show_actor_bboxes = false; this.show_actor_bboxes = false;
this.use_rewind_effect = false; this.use_rewind_effect = false;
this.perception = 'normal'; // normal, xray, editor, palette this.perception = 'normal'; // normal, xray, editor, palette
this.active_player = null;
} }
set_level(level) { set_level(level) {
@ -35,6 +36,10 @@ export class CanvasRenderer {
// TODO update viewport size... or maybe Game should do that since you might be cheating // TODO update viewport size... or maybe Game should do that since you might be cheating
} }
set_active_player(actor) {
this.active_player = actor;
}
// Change the viewport size. DOES NOT take effect until the next redraw! // Change the viewport size. DOES NOT take effect until the next redraw!
set_viewport_size(x, y) { set_viewport_size(x, y) {
this.viewport_size_x = x; this.viewport_size_x = x;
@ -176,19 +181,23 @@ export class CanvasRenderer {
vx = Math.floor(vx * tw + 0.5) / tw; vx = Math.floor(vx * tw + 0.5) / tw;
vy = Math.floor(vy * th + 0.5) / th; vy = Math.floor(vy * th + 0.5) / th;
// For actors (i.e., blocks), perception only applies if there's something of // For blocks, perception only applies if there's something of interest underneath
// potential interest underneath
let perception = this.perception; let perception = this.perception;
if (perception !== 'normal' && 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'; perception = 'normal';
} }
this.tileset.draw( let blit = this._make_tileset_blitter(this.ctx, vx - x0, vy - y0);
actor, tic, perception,
this._make_tileset_blitter(this.ctx, vx - x0, vy - y0)); // Draw the active player background
if (actor === this.active_player) {
this.tileset.draw_type('#active-player-background', null, tic, perception, blit);
}
this.tileset.draw(actor, tic, perception, blit);
} }
} }
for (let x = xf0; x <= x1; x++) { for (let x = xf0; x <= x1; x++) {

View File

@ -1,37 +1,14 @@
import { DIRECTIONS } from './defs.js'; import { DIRECTIONS } from './defs.js';
import TILE_TYPES from './tiletypes.js'; import TILE_TYPES from './tiletypes.js';
// TODO really need to specify this format more concretely, whoof // TODO move the remaining stuff (arrows, overlay i think, probably force floor thing) into specials
// XXX special kinds of drawing i know this has for a fact: // TODO more explicitly define animations, give them a speed! maybe fold directions into it
// - letter tiles draw from a block (one of two blocks!) of half-tiles onto the center of the base // TODO relatedly, the push animations are sometimes glitchy depending on when you start?
// - force floors are cropped from a double-size tile // TODO animate swimming player always
// - wired tiles are a whole thing (floor) // TODO life might be easier if i used the lynx-style loop with cooldown at the end
// - thin walls are packed into just two tiles // TODO define a draw state object to pass into here; need it for making turtles work right, fixing
// - directional blocks have arrows in an awkward layout, not 4x4 grid but actually positioned on the edges // blur with cc2 blobs/walkers, also makes a lot of signatures cleaner (make sure not slower)
// - green and purple toggle walls use an overlay // TODO monsters should only animate while moving? (not actually how cc2 works...)
// - 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
// - 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 = { export const CC2_TILESET_LAYOUT = {
'#wire-width': 1/16, '#wire-width': 1/16,
@ -144,9 +121,16 @@ export const CC2_TILESET_LAYOUT = {
3: [3, 4], 3: [3, 4],
4: [4, 4], 4: [4, 4],
}, },
bomb: [5, 4], bomb: {
green_bomb: [6, 4], special: 'bomb-fuse',
// TODO bomb fuse tile, ugh bomb: [5, 4],
fuse: [7, 4],
},
green_bomb: {
special: 'bomb-fuse',
bomb: [6, 4],
fuse: [7, 4],
},
floor_custom_green: [8, 4], floor_custom_green: [8, 4],
floor_custom_pink: [9, 4], floor_custom_pink: [9, 4],
floor_custom_yellow: [10, 4], floor_custom_yellow: [10, 4],
@ -181,7 +165,7 @@ export const CC2_TILESET_LAYOUT = {
suction_boots: [3, 6], suction_boots: [3, 6],
hiking_boots: [4, 6], hiking_boots: [4, 6],
lightning_bolt: [5, 6], lightning_bolt: [5, 6],
// FIXME draw the current player background... more external state for the renderer though... '#active-player-background': [6, 6],
// TODO dopps can push but i don't think they have any other visuals // TODO dopps can push but i don't think they have any other visuals
doppelganger1: { doppelganger1: {
base: [7, 6], base: [7, 6],
@ -313,26 +297,34 @@ export const CC2_TILESET_LAYOUT = {
foil: [12, 12], foil: [12, 12],
turtle: { turtle: {
// Turtles draw atop fake water, but don't act like water otherwise // Turtles draw atop fake water, but don't act like water otherwise
overlay: [13, 12], // TODO also 14 + 15 for sinking overlay: [13, 12], // TODO also 14 + 15, bobbing pseudorandomly
base: 'water', base: 'water',
}, },
walker: [0, 13], walker: {
// FIXME walker animations span multiple tiles special: 'double-size-monster',
base: [0, 13],
vertical: [[1, 13], [2, 13], [3, 13], [4, 13], [5, 13], [6, 13], [7, 13]],
horizontal: [[8, 13], [10, 13], [12, 13], [14, 13], [8, 14], [10, 14], [12, 14]],
},
helmet: [0, 14], helmet: [0, 14],
stopwatch_toggle: [14, 14], stopwatch_toggle: [14, 14],
stopwatch_bonus: [15, 14], stopwatch_bonus: [15, 14],
blob: [0, 15], blob: {
// FIXME blob animations span multiple tiles special: 'double-size-monster',
// TODO [0, 16] some kinda red/blue outline base: [0, 15],
vertical: [[1, 15], [2, 15], [3, 15], [4, 15], [5, 15], [6, 15], [7, 15]],
horizontal: [[8, 15], [10, 15], [12, 15], [14, 15], [8, 16], [10, 16], [12, 16]],
},
// (cc2 editor copy/paste outline)
floor_mimic: { floor_mimic: {
special: 'perception', special: 'perception',
modes: new Set(['palette', 'editor', 'xray']), modes: new Set(['palette', 'editor', 'xray']),
hidden: [0, 2], hidden: [0, 2],
revealed: [14, 16], revealed: [14, 16],
}, },
// TODO [15, 16] some kinda yellow/black outline // (cc2 editor cursor outline)
// timid teeth // timid teeth
teeth_timid: { teeth_timid: {
@ -352,24 +344,27 @@ export const CC2_TILESET_LAYOUT = {
west: [[14, 17], [15, 17]], west: [[14, 17], [15, 17]],
}, },
// TODO rover has an overlay showing its direction
rover: { rover: {
special: 'rover',
direction: [10, 18],
inert: [0, 18], inert: [0, 18],
teeth: [[0, 18], [8, 18]], teeth: [[0, 18], [8, 18]],
glider: [ // cw, slow
// quite fast glider: [[0, 18], [1, 18], [2, 18], [3, 18], [4, 18], [5, 18], [6, 18], [7, 18]],
[0, 18], [1, 18], [2, 18], [3, 18], [4, 18], [5, 18], [6, 18], [7, 18], // ccw, fast
[0, 18], [1, 18], [2, 18], [3, 18], [4, 18], [5, 18], [6, 18], [7, 18], bug: [
[7, 18], [6, 18], [5, 18], [4, 18], [3, 18], [2, 18], [1, 18], [0, 18],
[7, 18], [6, 18], [5, 18], [4, 18], [3, 18], [2, 18], [1, 18], [0, 18],
], ],
bug: [[0, 18], [1, 18], [2, 18], [3, 18], [4, 18], [5, 18], [6, 18], [7, 18]],
ball: [[0, 18], [4, 18]], ball: [[0, 18], [4, 18]],
teeth_timid: [[0, 18], [9, 18]], teeth_timid: [[0, 18], [9, 18]],
fireball: [ // ccw, slow
// quite fast fireball: [[7, 18], [6, 18], [5, 18], [4, 18], [3, 18], [2, 18], [1, 18], [0, 18]],
[7, 18], [6, 18], [5, 18], [4, 18], [3, 18], [2, 18], [1, 18], [0, 18], // cw, fast
[7, 18], [6, 18], [5, 18], [4, 18], [3, 18], [2, 18], [1, 18], [0, 18], paramecium: [
[0, 18], [1, 18], [2, 18], [3, 18], [4, 18], [5, 18], [6, 18], [7, 18],
[0, 18], [1, 18], [2, 18], [3, 18], [4, 18], [5, 18], [6, 18], [7, 18],
], ],
paramecium: [[7, 18], [6, 18], [5, 18], [4, 18], [3, 18], [2, 18], [1, 18], [0, 18]],
walker: [[8, 18], [9, 18]], walker: [[8, 18], [9, 18]],
}, },
xray_eye: [11, 18], xray_eye: [11, 18],
@ -424,19 +419,19 @@ export const CC2_TILESET_LAYOUT = {
west: [8, 23], west: [8, 23],
east: [8, 22], east: [8, 22],
}, },
blocked: 'pushing', blocked: {
north: [8, 24],
east: [9, 24],
south: [10, 24],
west: [11, 24],
},
moving: { moving: {
north: [[0, 22], [1, 22], [2, 22], [3, 22], [4, 22], [5, 22], [6, 22], [7, 22]], north: [[0, 22], [1, 22], [2, 22], [3, 22], [4, 22], [5, 22], [6, 22], [7, 22]],
east: [[8, 22], [9, 22], [10, 22], [11, 22], [12, 22], [13, 22], [14, 22], [15, 22]], east: [[8, 22], [9, 22], [10, 22], [11, 22], [12, 22], [13, 22], [14, 22], [15, 22]],
south: [[0, 23], [1, 23], [2, 23], [3, 23], [4, 23], [5, 23], [6, 23], [7, 23]], south: [[0, 23], [1, 23], [2, 23], [3, 23], [4, 23], [5, 23], [6, 23], [7, 23]],
west: [[8, 23], [9, 23], [10, 23], [11, 23], [12, 23], [13, 23], [14, 23], [15, 23]], west: [[8, 23], [9, 23], [10, 23], [11, 23], [12, 23], [13, 23], [14, 23], [15, 23]],
}, },
pushing: { pushing: 'blocked',
north: [8, 24],
east: [9, 24],
south: [10, 24],
west: [11, 24],
},
swimming: { swimming: {
north: [[0, 24], [1, 24]], north: [[0, 24], [1, 24]],
east: [[2, 24], [3, 24]], east: [[2, 24], [3, 24]],
@ -594,7 +589,6 @@ export const CC2_TILESET_LAYOUT = {
[15, 29], [15, 29],
], ],
// TODO handle train tracks! this is gonna be complicated.
railroad: { railroad: {
special: 'railroad', special: 'railroad',
base: [9, 10], base: [9, 10],
@ -817,6 +811,12 @@ export const LL_TILESET_LAYOUT = Object.assign({}, CC2_TILESET_LAYOUT, {
// Extra player sprites // Extra player sprites
player: Object.assign({}, CC2_TILESET_LAYOUT.player, { player: Object.assign({}, CC2_TILESET_LAYOUT.player, {
pushing: {
north: [[8, 24], [0, 34], [8, 24], [1, 34]],
east: [[9, 24], [2, 34], [9, 24], [3, 34]],
south: [[10, 24], [4, 34], [10, 24], [5, 34]],
west: [[11, 24], [6, 34], [11, 24], [7, 34]],
},
skating: { skating: {
north: [0, 33], north: [0, 33],
east: [1, 33], east: [1, 33],
@ -834,7 +834,19 @@ export const LL_TILESET_LAYOUT = Object.assign({}, CC2_TILESET_LAYOUT, {
slimed: [1, 38], slimed: [1, 38],
}), }),
player2: Object.assign({}, CC2_TILESET_LAYOUT.player2, { player2: Object.assign({}, CC2_TILESET_LAYOUT.player2, {
// TODO skating pushing: {
north: [[8, 29], [8, 34], [8, 29], [9, 34]],
east: [[9, 29], [10, 34], [9, 29], [11, 34]],
south: [[10, 29], [12, 34], [10, 29], [13, 34]],
west: [[11, 29], [14, 34], [11, 29], [15, 34]],
},
skating: {
north: [8, 33],
east: [9, 33],
south: [10, 33],
west: [11, 33],
},
forced: 'skating',
exited: [15, 32], exited: [15, 32],
burned: { burned: {
north: [12, 33], north: [12, 33],
@ -880,9 +892,19 @@ export const LL_TILESET_LAYOUT = Object.assign({}, CC2_TILESET_LAYOUT, {
// Custom VFX // Custom VFX
splash_slime: [[0, 38], [1, 38], [2, 38], [3, 38]], splash_slime: [[0, 38], [1, 38], [2, 38], [3, 38]],
teleport_flash: [[4, 38], [5, 38], [6, 38], [7, 38]], teleport_flash: [[4, 38], [5, 38], [6, 38], [7, 38]],
chip_extra: {
special: 'perception',
modes: new Set(['palette', 'editor']),
hidden: [[11, 3], [0, 39], [1, 39], [0, 39]],
revealed: [10, 3],
},
chip: [[11, 3], [0, 39], [1, 39], [0, 39]],
green_chip: [[9, 3], [2, 39], [3, 39], [2, 39]],
// FIXME make these work with a stock tileset
player1_exit: [[8, 38], [9, 38], [10, 38], [11, 38]], player1_exit: [[8, 38], [9, 38], [10, 38], [11, 38]],
player2_exit: [[12, 38], [13, 38], [14, 38], [15, 38]], player2_exit: [[12, 38], [13, 38], [14, 38], [15, 38]],
transmogrify_flash: [[4, 39], [5, 39], [6, 39], [7, 39]], puff: [[4, 39], [5, 39], [6, 39], [7, 39]],
transmogrify_flash: [[8, 39], [9, 39], [10, 39], [11, 39], [12, 39], [13, 39], [14, 39], [15, 39]],
// More custom tiles // More custom tiles
gate_red: [0, 40], gate_red: [0, 40],
@ -921,46 +943,44 @@ export class Tileset {
// Deal with animation // Deal with animation
if (coords[0] instanceof Array) { if (coords[0] instanceof Array) {
if (tic !== null) { if (tic === null) {
if (tile && tile.movement_speed) { coords = coords[0];
// This tile reports its own animation timing (in frames), so trust that, and }
// just use the current tic's fraction. else if (tile && tile.movement_speed) {
// That said: adjusting animation speed complicates this slightly. Consider the // This tile reports its own animation timing (in frames), so trust that, and use
// player's walk animation, which takes 4 tics to complete, during which time we // the current tic's fraction. If we're between tics, interpolate.
// cycle through 8 frames. Playing that at half speed means only half the // FIXME if the game ever runs every frame we will have to adjust the interpolation
// animation actually plays, but if the player continues walking, then on the let p = ((tile.movement_speed - tile.movement_cooldown) + tic % 1 * 3) / tile.movement_speed;
// NEXT four tics, we should play the other half. To make this work, use the if (this.animation_slowdown > 1 && ! tile.type.ttl) {
// tic as a global timer as well: if the animation started on tics 0-4, play the // The players have full walk animations, but they look very silly when squeezed
// first half; if it started on tics 5-8, play the second half. They could get // into the span of a single step, so instead we only play half at a time. The
// out of sync if the player hesitates, but no one will notice that, and this // halves alternate, so the player still sees the whole animation when walking
// approach minimizes storing extra state. // continuously. To make this work, consider: p, the current progress through
let i = ((tile.movement_speed - tile.movement_cooldown) + tic % 1 * 3) / tile.movement_speed; // the animation, is in [0, 1). To play the first half, we want [0, 0.5); to
// FIXME hack for cc2 mode, the only place we can see a cooldown of 0 which // play the second half, we want [0.5, 1). Thus we add an integer in [0, 2) to
// makes i be 1 // offset us into which half to play, then divide by 2 to renormalize.
i = Math.min(0.999, i); // Which half to use is determined by when the animation /started/, as measured
// But do NOT do this for explosions or splashes, which have a fixed duration // in animation lengths.
// and only play once let start_time = (tic * 3 / tile.movement_speed) - p;
if (this.animation_slowdown > 1 && ! tile.type.ttl) { // Rounding smooths out float error (assuming the framerate never exceeds 1000)
// i ranges from [0, 1), but a slowdown of N means we'll only play the first let segment = Math.floor(Math.round(start_time * 1000) / 1000 % this.animation_slowdown);
// 1/N of it before the game ends (or loops) the animation. p = (p + segment) / this.animation_slowdown;
// So increase by [0..N-1] to get it in some other range, then divide by N
// to scale back down to [0, 1)
i += Math.floor(tic * 3 / tile.movement_speed % this.animation_slowdown);
i /= this.animation_slowdown;
}
coords = coords[Math.floor(i * coords.length)];
} }
else if (tile && tile.type.movement_speed) { // Lexy runs cooldown from S to 1; CC2 from S-1 to 0. 0 is bad, because p becomes 1
// This is an actor that's not moving, so use the first frame // and will overflow the cel lookup
coords = coords[0]; // FIXME handle this better! it happens even to lexy
} if (p >= 1) {
else { p = 0.999;
// 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(p * coords.length)];
}
else if (tile && tile.type.movement_speed) {
// This is an actor that's not moving, so use the first frame
coords = coords[0];
} }
else { else {
coords = coords[0]; // 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)];
} }
} }
@ -1032,6 +1052,91 @@ export class Tileset {
} }
} }
_draw_bomb_fuse(drawspec, tile, tic, blit) {
// Draw the base bomb
this._draw_standard(drawspec.bomb, tile, tic, blit);
// 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.
// 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.)
// Also it's drawn in the upper right, that's important.
let cel = Math.floor(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);
}
_draw_double_size_monster(drawspec, tile, tic, blit) {
// 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.
// First, of course, this only happens if they're moving at all.
if (! tile || ! tile.movement_speed) {
this._draw_standard(drawspec.base, tile, tic, blit);
return;
}
// They only support horizontal and vertical moves, not all four directions. The other two
// directions are simply the animations played in reverse.
let axis_cels;
let w = 1, h = 1, x = 0, y = 0, reverse = false;
if (tile.direction === 'north') {
axis_cels = drawspec.vertical;
reverse = true;
h = 2;
}
else if (tile.direction === 'south') {
axis_cels = drawspec.vertical;
h = 2;
}
else if (tile.direction === 'west') {
axis_cels = drawspec.horizontal;
reverse = true;
w = 2;
}
else if (tile.direction === 'east') {
axis_cels = drawspec.horizontal;
w = 2;
}
// FIXME lexy is n to 1, cc2 n-1 to 0, and this mixes them
let p = tile.movement_speed - tile.movement_cooldown;
p = (p + tic % 1 * 3) / tile.movement_speed;
p = Math.min(p, 0.999); // FIXME hack for differing movement counters
let index = Math.floor(p * (axis_cels.length + 1));
if (index === 0 || index > axis_cels.length) {
this._draw_standard(drawspec.base, tile, tic, blit);
}
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];
blit(...cel, 0, 0, w, h, x - vx % 1, y - vy % 1);
}
}
_draw_rover(drawspec, tile, tic, blit) {
// Rovers draw fairly normally (with their visual_state giving the monster they're copying),
// but they also have an overlay indicating their direction
let state = tile ? tile.type.visual_state(tile) : 'inert';
this._draw_standard(drawspec[state], tile, tic, blit);
if (! tile)
return;
// The direction overlay is one of four quarter-tiles, drawn about in the center of the
// rover but shifted an eighth of a tile in the direction in question
let overlay_position = this._rotate(tile.direction, 0.25, 0.125, 0.75, 0.625);
let index = {north: 0, east: 1, west: 2, south: 3}[tile.direction];
if (index === undefined)
return;
blit(
...drawspec.direction,
0.5 * (index % 2), 0.5 * Math.floor(index / 2), 0.5, 0.5,
overlay_position[0], overlay_position[1]);
}
_draw_logic_gate(drawspec, tile, tic, blit) { _draw_logic_gate(drawspec, tile, tic, blit) {
// Layer 1: wiring state // Layer 1: wiring state
// Always draw the unpowered wire base // Always draw the unpowered wire base
@ -1190,6 +1295,18 @@ export class Tileset {
this._draw_thin_walls_cc1(drawspec, tile, tic, blit); this._draw_thin_walls_cc1(drawspec, tile, tic, blit);
return; return;
} }
else if (drawspec.special === 'bomb-fuse') {
this._draw_bomb_fuse(drawspec, tile, tic, blit);
return;
}
else if (drawspec.special === 'double-size-monster') {
this._draw_double_size_monster(drawspec, tile, tic, blit);
return;
}
else if (drawspec.special === 'rover') {
this._draw_rover(drawspec, tile, tic, blit);
return;
}
else if (drawspec.special === 'perception') { else if (drawspec.special === 'perception') {
if (drawspec.modes.has(perception)) { if (drawspec.modes.has(perception)) {
drawspec = drawspec.revealed; drawspec = drawspec.revealed;

View File

@ -62,6 +62,7 @@ function _define_door(key) {
level.take_tool_from_actor(other, 'skeleton_key')) level.take_tool_from_actor(other, 'skeleton_key'))
{ {
level.sfx.play_once('door', me.cell); level.sfx.play_once('door', me.cell);
level.spawn_animation(me.cell, 'puff');
level.transmute_tile(me, 'floor'); level.transmute_tile(me, 'floor');
} }
}, },
@ -83,6 +84,7 @@ function _define_gate(key) {
level.take_tool_from_actor(other, 'skeleton_key')) level.take_tool_from_actor(other, 'skeleton_key'))
{ {
level.sfx.play_once('door', me.cell); level.sfx.play_once('door', me.cell);
level.spawn_animation(me.cell, 'puff');
level.remove_tile(me); level.remove_tile(me);
} }
}, },
@ -271,6 +273,7 @@ const TILE_TYPES = {
} }
}, },
on_depart(me, level, other) { on_depart(me, level, other) {
level.spawn_animation(me.cell, 'puff');
level.transmute_tile(me, 'wall'); level.transmute_tile(me, 'wall');
}, },
}, },
@ -280,6 +283,7 @@ const TILE_TYPES = {
layer: LAYERS.terrain, layer: LAYERS.terrain,
blocks_collision: COLLISION.block_cc1 | COLLISION.monster_solid, blocks_collision: COLLISION.block_cc1 | COLLISION.monster_solid,
on_depart(me, level, other) { on_depart(me, level, other) {
level.spawn_animation(me.cell, 'puff');
level.transmute_tile(me, 'popwall'); level.transmute_tile(me, 'popwall');
}, },
}, },
@ -320,6 +324,7 @@ const TILE_TYPES = {
blocks_collision: COLLISION.block_cc1 | (COLLISION.monster_solid & ~COLLISION.rover), blocks_collision: COLLISION.block_cc1 | (COLLISION.monster_solid & ~COLLISION.rover),
on_bumped(me, level, other) { on_bumped(me, level, other) {
if (other.type.can_reveal_walls) { if (other.type.can_reveal_walls) {
level.spawn_animation(me.cell, 'puff');
level.transmute_tile(me, 'floor'); level.transmute_tile(me, 'floor');
} }
}, },
@ -2625,6 +2630,7 @@ const TILE_TYPES = {
on_arrive(me, level, other) { on_arrive(me, level, other) {
if (level.chips_remaining === 0) { if (level.chips_remaining === 0) {
level.sfx.play_once('socket', me.cell); level.sfx.play_once('socket', me.cell);
level.spawn_animation(me.cell, 'puff');
level.transmute_tile(me, 'floor'); level.transmute_tile(me, 'floor');
} }
}, },
@ -2712,7 +2718,13 @@ const TILE_TYPES = {
layer: LAYERS.vfx, layer: LAYERS.vfx,
is_actor: true, is_actor: true,
collision_mask: 0, collision_mask: 0,
ttl: 6 * 3, ttl: 4 * 3,
},
puff: {
layer: LAYERS.vfx,
is_actor: true,
collision_mask: 0,
ttl: 4 * 3,
}, },
// Invalid tiles that appear in some CCL levels because community level // Invalid tiles that appear in some CCL levels because community level

Binary file not shown.

Before

Width:  |  Height:  |  Size: 73 KiB

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.