Make animations explicit, and fix almost every rendering problem

- `Tileset.animation_slowdown` is gone!
- Actors are now free to animate every move or every two moves, and have
  been configured to do so appropriately.  This fixes the appearance of
  blobs, most noticeably.  (Also fixes #36.)
- Actors that are always animated in CC2 are now always animated in LL.
- Lilypads/turtles now bob randomly.  More randomly than CC2, even.
- Players no longer appear to be swimming when stepping off of lilypads.
- Invisible walls no longer temporarily reveal themselves when you have
  the x-ray glasses (secret eye).
- There's a new option for using the CC2 animation timing, though god
  knows why you would want to.
This commit is contained in:
Eevee (Evelyn Woods) 2021-03-06 18:41:49 -07:00
parent 8533eac5db
commit 26c66d6857
5 changed files with 614 additions and 211 deletions

View File

@ -1092,6 +1092,9 @@ export class Level extends LevelInterface {
if (actor.is_pulled) { if (actor.is_pulled) {
this._set_tile_prop(actor, 'is_pulled', false); this._set_tile_prop(actor, 'is_pulled', false);
} }
if (actor.not_swimming) {
this._set_tile_prop(actor, 'not_swimming', false);
}
} }
if (actor === this.player) { if (actor === this.player) {

View File

@ -533,6 +533,8 @@ class Player extends PrimaryView {
this.use_interpolation = true; this.use_interpolation = true;
// Default to the LL tileset for safety, but change when we load a level // Default to the LL tileset for safety, but change when we load a level
// (Note also that this must be created in the constructor so the CC2 timing option can be
// applied to it)
this.renderer = new CanvasRenderer(this.conductor.tilesets['ll']); this.renderer = new CanvasRenderer(this.conductor.tilesets['ll']);
this._loaded_tileset = false; this._loaded_tileset = false;
this.level_el.append(this.renderer.canvas); this.level_el.append(this.renderer.canvas);
@ -1212,6 +1214,7 @@ class Player extends PrimaryView {
this.music_enabled = options.music_enabled ?? true; this.music_enabled = options.music_enabled ?? true;
this.sfx_player.volume = options.sound_volume ?? 1.0; this.sfx_player.volume = options.sound_volume ?? 1.0;
this.sfx_player.enabled = options.sound_enabled ?? true; this.sfx_player.enabled = options.sound_enabled ?? true;
this.renderer.use_cc2_anim_speed = options.use_cc2_anim_speed ?? false;
if (this.level) { if (this.level) {
this.update_tileset(); this.update_tileset();
@ -2584,6 +2587,8 @@ class OptionsOverlay extends DialogOverlay {
mk('label', mk('input', {name: 'sound-enabled', type: 'checkbox'}), " Enabled"), mk('label', mk('input', {name: 'sound-enabled', type: 'checkbox'}), " Enabled"),
mk('input', {name: 'sound-volume', type: 'range', min: 0, max: 1, step: 0.05}), mk('input', {name: 'sound-volume', type: 'range', min: 0, max: 1, step: 0.05}),
), ),
mk('dt'),
mk('dd', mk('label', mk('input', {name: 'use-cc2-anim-speed', type: 'checkbox'}), " Use CC2 animation speed")),
); );
// Update volume live, if the player is active and was playing when this dialog was opened // Update volume live, if the player is active and was playing when this dialog was opened
// (note that it won't auto-pause until open()) // (note that it won't auto-pause until open())
@ -2692,6 +2697,7 @@ class OptionsOverlay extends DialogOverlay {
this.root.elements['music-enabled'].checked = this.conductor.options.music_enabled ?? true; this.root.elements['music-enabled'].checked = this.conductor.options.music_enabled ?? true;
this.root.elements['sound-volume'].value = this.conductor.options.sound_volume ?? 1.0; this.root.elements['sound-volume'].value = this.conductor.options.sound_volume ?? 1.0;
this.root.elements['sound-enabled'].checked = this.conductor.options.sound_enabled ?? true; this.root.elements['sound-enabled'].checked = this.conductor.options.sound_enabled ?? true;
this.root.elements['use-cc2-anim-speed'].checked = this.conductor.options.use_cc2_anim_speed ?? false;
this.root.elements['custom-tileset'].addEventListener('change', ev => { this.root.elements['custom-tileset'].addEventListener('change', ev => {
this._load_custom_tileset(ev.target.files[0]); this._load_custom_tileset(ev.target.files[0]);
@ -2703,6 +2709,7 @@ class OptionsOverlay extends DialogOverlay {
options.music_enabled = this.root.elements['music-enabled'].checked; options.music_enabled = this.root.elements['music-enabled'].checked;
options.sound_volume = parseFloat(this.root.elements['sound-volume'].value); options.sound_volume = parseFloat(this.root.elements['sound-volume'].value);
options.sound_enabled = this.root.elements['sound-enabled'].checked; options.sound_enabled = this.root.elements['sound-enabled'].checked;
options.use_cc2_anim_speed = this.root.elements['use-cc2-anim-speed'].checked;
// Tileset stuff: slightly more complicated. Save custom ones to localStorage as data // Tileset stuff: slightly more complicated. Save custom ones to localStorage as data
// URIs, and /delete/ any custom ones we're not using any more, both of which require // URIs, and /delete/ any custom ones we're not using any more, both of which require

View File

@ -14,6 +14,8 @@ class CanvasRendererDrawPacket extends DrawPacket {
// Offset within the cell, for actors in motion // Offset within the cell, for actors in motion
this.offsetx = 0; this.offsetx = 0;
this.offsety = 0; this.offsety = 0;
// Compatibility settings
this.use_cc2_anim_speed = renderer.use_cc2_anim_speed;
} }
blit(tx, ty, mx = 0, my = 0, mw = 1, mh = mw, mdx = mx, mdy = my) { blit(tx, ty, mx = 0, my = 0, mw = 1, mh = mw, mdx = mx, mdy = my) {
@ -60,6 +62,7 @@ export class CanvasRenderer {
this.show_actor_order = false; this.show_actor_order = 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.use_cc2_anim_speed = false;
this.active_player = null; this.active_player = null;
} }

File diff suppressed because it is too large Load Diff

View File

@ -146,9 +146,12 @@ function player_visual_state(me) {
else if (me.exited) { else if (me.exited) {
return 'exited'; return 'exited';
} }
else if (me.cell && (me.previous_cell || me.cell).has('water')) { // This is slightly complicated. We should show a swimming pose while still in water, or moving
// CC2 shows a swimming pose while still in water, or moving away from water // away from water (as CC2 does), but NOT when stepping off a lilypad (which will already have
// FIXME this also shows in some cases when we don't have flippers, e.g. when starting in water // been turned into water), and NOT without flippers (which can happen if we start on water)
else if (me.cell && (me.previous_cell || me.cell).has('water') &&
! me.not_swimming && me.has_item('flippers'))
{
return 'swimming'; return 'swimming';
} }
else if (me.slide_mode === 'ice') { else if (me.slide_mode === 'ice') {
@ -833,6 +836,9 @@ const TILE_TYPES = {
level.transmute_tile(me, 'water'); level.transmute_tile(me, 'water');
level.spawn_animation(me.cell, 'splash'); level.spawn_animation(me.cell, 'splash');
level.sfx.play_once('splash', me.cell); level.sfx.play_once('splash', me.cell);
// Visual property, so the actor knows it's stepping off a lilypad, not swimming out of
// the water we just turned into
level._set_tile_prop(other, 'not_swimming', true);
}, },
}, },
cracked_ice: { cracked_ice: {