diff --git a/js/main-editor.js b/js/main-editor.js index 853dcb0..a52569f 100644 --- a/js/main-editor.js +++ b/js/main-editor.js @@ -3127,6 +3127,7 @@ export class Editor extends PrimaryView { // Automatically redraw only what's changed _redraw_dirty() { + // TODO draw sparkle background under the starting player if (this._dirty_rect) { this.renderer.draw_static_region( this._dirty_rect.left, this._dirty_rect.top, diff --git a/js/main.js b/js/main.js index 08b6f7b..760a3d5 100644 --- a/js/main.js +++ b/js/main.js @@ -1352,6 +1352,8 @@ class Player extends PrimaryView { } 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 for (let [key, nodes] of Object.entries(this.inventory_key_nodes)) { 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), }); this.renderer.set_level(level); + this.renderer.set_active_player(level.player); this.renderer.draw(); canvas.getContext('2d').drawImage( this.renderer.canvas, 0, 0, diff --git a/js/renderer-canvas.js b/js/renderer-canvas.js index a71b069..1fdfad0 100644 --- a/js/renderer-canvas.js +++ b/js/renderer-canvas.js @@ -28,6 +28,7 @@ export class CanvasRenderer { this.show_actor_bboxes = false; this.use_rewind_effect = false; this.perception = 'normal'; // normal, xray, editor, palette + this.active_player = null; } 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 } + set_active_player(actor) { + this.active_player = actor; + } + // Change the viewport size. DOES NOT take effect until the next redraw! set_viewport_size(x, y) { this.viewport_size_x = x; @@ -176,19 +181,23 @@ export class CanvasRenderer { vx = Math.floor(vx * tw + 0.5) / tw; vy = Math.floor(vy * th + 0.5) / th; - // For actors (i.e., blocks), perception only applies if there's something of - // potential interest underneath + // For blocks, perception only applies if there's something of interest underneath let perception = this.perception; - if (perception !== 'normal' && + if (perception !== 'normal' && actor.type.is_block && ! cell.some(t => t && t.type.layer < LAYERS.actor && ! ( t.type.name === 'floor' && (t.wire_directions | t.wire_tunnel_directions) === 0))) { perception = 'normal'; } - this.tileset.draw( - actor, tic, perception, - this._make_tileset_blitter(this.ctx, vx - x0, vy - y0)); + let blit = 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++) { diff --git a/js/tileset.js b/js/tileset.js index 539757d..0963499 100644 --- a/js/tileset.js +++ b/js/tileset.js @@ -1,37 +1,14 @@ import { DIRECTIONS } from './defs.js'; 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 (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 (floor) -// - thin walls are packed into just two tiles -// - 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, 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 +// TODO move the remaining stuff (arrows, overlay i think, probably force floor thing) into specials +// TODO more explicitly define animations, give them a speed! maybe fold directions into it +// TODO relatedly, the push animations are sometimes glitchy depending on when you start? +// TODO animate swimming player always +// TODO life might be easier if i used the lynx-style loop with cooldown at the end +// TODO define a draw state object to pass into here; need it for making turtles work right, fixing +// blur with cc2 blobs/walkers, also makes a lot of signatures cleaner (make sure not slower) +// TODO monsters should only animate while moving? (not actually how cc2 works...) export const CC2_TILESET_LAYOUT = { '#wire-width': 1/16, @@ -144,9 +121,16 @@ export const CC2_TILESET_LAYOUT = { 3: [3, 4], 4: [4, 4], }, - bomb: [5, 4], - green_bomb: [6, 4], - // TODO bomb fuse tile, ugh + bomb: { + special: 'bomb-fuse', + bomb: [5, 4], + fuse: [7, 4], + }, + green_bomb: { + special: 'bomb-fuse', + bomb: [6, 4], + fuse: [7, 4], + }, floor_custom_green: [8, 4], floor_custom_pink: [9, 4], floor_custom_yellow: [10, 4], @@ -181,7 +165,7 @@ export const CC2_TILESET_LAYOUT = { suction_boots: [3, 6], hiking_boots: [4, 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 doppelganger1: { base: [7, 6], @@ -313,26 +297,34 @@ export const CC2_TILESET_LAYOUT = { foil: [12, 12], turtle: { // 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', }, - walker: [0, 13], - // FIXME walker animations span multiple tiles + walker: { + 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], stopwatch_toggle: [14, 14], stopwatch_bonus: [15, 14], - blob: [0, 15], - // FIXME blob animations span multiple tiles - // TODO [0, 16] some kinda red/blue outline + blob: { + special: 'double-size-monster', + 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: { special: 'perception', modes: new Set(['palette', 'editor', 'xray']), hidden: [0, 2], revealed: [14, 16], }, - // TODO [15, 16] some kinda yellow/black outline + // (cc2 editor cursor outline) // timid teeth teeth_timid: { @@ -352,24 +344,27 @@ export const CC2_TILESET_LAYOUT = { west: [[14, 17], [15, 17]], }, - // TODO rover has an overlay showing its direction rover: { + special: 'rover', + direction: [10, 18], inert: [0, 18], teeth: [[0, 18], [8, 18]], - glider: [ - // quite fast - [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], + // cw, slow + glider: [[0, 18], [1, 18], [2, 18], [3, 18], [4, 18], [5, 18], [6, 18], [7, 18]], + // ccw, fast + 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]], teeth_timid: [[0, 18], [9, 18]], - fireball: [ - // quite fast - [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], + // ccw, slow + fireball: [[7, 18], [6, 18], [5, 18], [4, 18], [3, 18], [2, 18], [1, 18], [0, 18]], + // cw, fast + 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]], }, xray_eye: [11, 18], @@ -424,19 +419,19 @@ export const CC2_TILESET_LAYOUT = { west: [8, 23], east: [8, 22], }, - blocked: 'pushing', + blocked: { + north: [8, 24], + east: [9, 24], + south: [10, 24], + west: [11, 24], + }, moving: { 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]], 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]], }, - pushing: { - north: [8, 24], - east: [9, 24], - south: [10, 24], - west: [11, 24], - }, + pushing: 'blocked', swimming: { north: [[0, 24], [1, 24]], east: [[2, 24], [3, 24]], @@ -594,7 +589,6 @@ export const CC2_TILESET_LAYOUT = { [15, 29], ], - // TODO handle train tracks! this is gonna be complicated. railroad: { special: 'railroad', base: [9, 10], @@ -817,6 +811,12 @@ export const LL_TILESET_LAYOUT = Object.assign({}, CC2_TILESET_LAYOUT, { // Extra player sprites 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: { north: [0, 33], east: [1, 33], @@ -834,7 +834,19 @@ export const LL_TILESET_LAYOUT = Object.assign({}, CC2_TILESET_LAYOUT, { slimed: [1, 38], }), 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], burned: { north: [12, 33], @@ -880,9 +892,19 @@ export const LL_TILESET_LAYOUT = Object.assign({}, CC2_TILESET_LAYOUT, { // Custom VFX splash_slime: [[0, 38], [1, 38], [2, 38], [3, 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]], 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 gate_red: [0, 40], @@ -921,46 +943,44 @@ export class Tileset { // Deal with animation if (coords[0] instanceof Array) { - if (tic !== null) { - if (tile && tile.movement_speed) { - // This tile reports its own animation timing (in frames), so trust that, and - // just use the current tic's fraction. - // That said: adjusting animation speed complicates this slightly. Consider the - // player's walk animation, which takes 4 tics to complete, during which time we - // cycle through 8 frames. Playing that at half speed means only half the - // animation actually plays, but if the player continues walking, then on the - // NEXT four tics, we should play the other half. To make this work, use the - // tic as a global timer as well: if the animation started on tics 0-4, play the - // first half; if it started on tics 5-8, play the second half. They could get - // out of sync if the player hesitates, but no one will notice that, and this - // approach minimizes storing extra state. - let i = ((tile.movement_speed - tile.movement_cooldown) + tic % 1 * 3) / tile.movement_speed; - // FIXME hack for cc2 mode, the only place we can see a cooldown of 0 which - // makes i be 1 - i = Math.min(0.999, i); - // But do NOT do this for explosions or splashes, which have a fixed duration - // and only play once - if (this.animation_slowdown > 1 && ! tile.type.ttl) { - // i ranges from [0, 1), but a slowdown of N means we'll only play the first - // 1/N of it before the game ends (or loops) the animation. - // 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)]; + if (tic === null) { + coords = coords[0]; + } + else if (tile && tile.movement_speed) { + // 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. + // 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; + if (this.animation_slowdown > 1 && ! tile.type.ttl) { + // 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 + // halves alternate, so the player still sees the whole animation when walking + // continuously. To make this work, consider: p, the current progress through + // the animation, is in [0, 1). To play the first half, we want [0, 0.5); to + // play the second half, we want [0.5, 1). Thus we add an integer in [0, 2) to + // 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 + // in animation lengths. + let start_time = (tic * 3 / tile.movement_speed) - p; + // Rounding smooths out float error (assuming the framerate never exceeds 1000) + let segment = Math.floor(Math.round(start_time * 1000) / 1000 % this.animation_slowdown); + p = (p + segment) / this.animation_slowdown; } - else if (tile && tile.type.movement_speed) { - // This is an actor that's not moving, so use the first frame - coords = coords[0]; - } - else { - // 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)]; + // Lexy runs cooldown from S to 1; CC2 from S-1 to 0. 0 is bad, because p becomes 1 + // and will overflow the cel lookup + // FIXME handle this better! it happens even to lexy + if (p >= 1) { + p = 0.999; } + 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 { - 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) { // Layer 1: wiring state // Always draw the unpowered wire base @@ -1190,6 +1295,18 @@ export class Tileset { this._draw_thin_walls_cc1(drawspec, tile, tic, blit); 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') { if (drawspec.modes.has(perception)) { drawspec = drawspec.revealed; diff --git a/js/tiletypes.js b/js/tiletypes.js index 09a5ace..f27b3da 100644 --- a/js/tiletypes.js +++ b/js/tiletypes.js @@ -62,6 +62,7 @@ function _define_door(key) { level.take_tool_from_actor(other, 'skeleton_key')) { level.sfx.play_once('door', me.cell); + level.spawn_animation(me.cell, 'puff'); level.transmute_tile(me, 'floor'); } }, @@ -83,6 +84,7 @@ function _define_gate(key) { level.take_tool_from_actor(other, 'skeleton_key')) { level.sfx.play_once('door', me.cell); + level.spawn_animation(me.cell, 'puff'); level.remove_tile(me); } }, @@ -271,6 +273,7 @@ const TILE_TYPES = { } }, on_depart(me, level, other) { + level.spawn_animation(me.cell, 'puff'); level.transmute_tile(me, 'wall'); }, }, @@ -280,6 +283,7 @@ const TILE_TYPES = { layer: LAYERS.terrain, blocks_collision: COLLISION.block_cc1 | COLLISION.monster_solid, on_depart(me, level, other) { + level.spawn_animation(me.cell, 'puff'); level.transmute_tile(me, 'popwall'); }, }, @@ -320,6 +324,7 @@ const TILE_TYPES = { blocks_collision: COLLISION.block_cc1 | (COLLISION.monster_solid & ~COLLISION.rover), on_bumped(me, level, other) { if (other.type.can_reveal_walls) { + level.spawn_animation(me.cell, 'puff'); level.transmute_tile(me, 'floor'); } }, @@ -2625,6 +2630,7 @@ const TILE_TYPES = { on_arrive(me, level, other) { if (level.chips_remaining === 0) { level.sfx.play_once('socket', me.cell); + level.spawn_animation(me.cell, 'puff'); level.transmute_tile(me, 'floor'); } }, @@ -2712,7 +2718,13 @@ const TILE_TYPES = { layer: LAYERS.vfx, is_actor: true, 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 diff --git a/tileset-lexy.png b/tileset-lexy.png index 84f49fc..f70ab7c 100644 Binary files a/tileset-lexy.png and b/tileset-lexy.png differ diff --git a/tileset-src/tileset-lexy-lexy-push.aseprite b/tileset-src/tileset-lexy-lexy-push.aseprite new file mode 100644 index 0000000..2c41893 Binary files /dev/null and b/tileset-src/tileset-lexy-lexy-push.aseprite differ diff --git a/tileset-src/tileset-lexy-puff.aseprite b/tileset-src/tileset-lexy-puff.aseprite new file mode 100644 index 0000000..8b4175a Binary files /dev/null and b/tileset-src/tileset-lexy-puff.aseprite differ diff --git a/tileset-src/tileset-lexy-transmogrifier.aseprite b/tileset-src/tileset-lexy-transmogrifier.aseprite index 4862a94..2c3e0aa 100644 Binary files a/tileset-src/tileset-lexy-transmogrifier.aseprite and b/tileset-src/tileset-lexy-transmogrifier.aseprite differ diff --git a/tileset-src/tileset-lexy-vfx-transmogrify.aseprite b/tileset-src/tileset-lexy-vfx-transmogrify.aseprite index 6844faa..59029c3 100644 Binary files a/tileset-src/tileset-lexy-vfx-transmogrify.aseprite and b/tileset-src/tileset-lexy-vfx-transmogrify.aseprite differ diff --git a/tileset-src/tileset-lexy.aseprite b/tileset-src/tileset-lexy.aseprite index dd95b88..c5fb80f 100644 Binary files a/tileset-src/tileset-lexy.aseprite and b/tileset-src/tileset-lexy.aseprite differ