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:
parent
31a1049655
commit
aed96c8e41
@ -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,
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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++) {
|
||||||
|
|||||||
315
js/tileset.js
315
js/tileset.js
@ -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;
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
BIN
tileset-lexy.png
BIN
tileset-lexy.png
Binary file not shown.
|
Before Width: | Height: | Size: 73 KiB After Width: | Height: | Size: 76 KiB |
BIN
tileset-src/tileset-lexy-lexy-push.aseprite
Normal file
BIN
tileset-src/tileset-lexy-lexy-push.aseprite
Normal file
Binary file not shown.
BIN
tileset-src/tileset-lexy-puff.aseprite
Normal file
BIN
tileset-src/tileset-lexy-puff.aseprite
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Loading…
Reference in New Issue
Block a user