From 9257fe4ee577622ae8987dd5b8663bd3737734d2 Mon Sep 17 00:00:00 2001 From: Jordan Irwin Date: Sun, 7 Apr 2024 21:19:29 -0700 Subject: [PATCH] Add support for parallax scrolling backgrounds in web client --- buildtools/ant_modules/dist.xml | 4 + doc/CHANGES.txt | 1 + src/js/stendhal/Client.ts | 3 + src/js/stendhal/data/Map.ts | 23 +++++ src/js/stendhal/data/Paths.ts | 1 + .../stendhal/landscape/ParallaxBackground.ts | 98 +++++++++++++++++++ src/js/stendhal/ui/ViewPort.ts | 1 + 7 files changed, 131 insertions(+) create mode 100644 src/js/stendhal/landscape/ParallaxBackground.ts diff --git a/buildtools/ant_modules/dist.xml b/buildtools/ant_modules/dist.xml index 33553c9a8d2..5acb712239d 100644 --- a/buildtools/ant_modules/dist.xml +++ b/buildtools/ant_modules/dist.xml @@ -132,6 +132,10 @@ + + + + diff --git a/doc/CHANGES.txt b/doc/CHANGES.txt index e53232f8a0a..22a439743d2 100644 --- a/doc/CHANGES.txt +++ b/doc/CHANGES.txt @@ -10,6 +10,7 @@ Changelog *web client* - added visuals tab to settings dialog - animated fog +- added support for parallax scrolling backgrounds 1.47 diff --git a/src/js/stendhal/Client.ts b/src/js/stendhal/Client.ts index 6f2caa757c5..524347cef0e 100644 --- a/src/js/stendhal/Client.ts +++ b/src/js/stendhal/Client.ts @@ -476,6 +476,9 @@ export class Client { stendhal.sound.playSingleGlobalizedMusic(zoneinfo["music"], !Number.isNaN(musicVolume) ? musicVolume : 1.0); + // parallax background + stendhal.data.map.setParallax(zoneinfo["parallax"]); + // coloring information if (zoneinfo["color"] && stendhal.config.getBoolean("effect.lighting")) { if (zoneinfo["color_method"]) { diff --git a/src/js/stendhal/data/Map.ts b/src/js/stendhal/data/Map.ts index 2e493b768dd..a6d7415caf3 100644 --- a/src/js/stendhal/data/Map.ts +++ b/src/js/stendhal/data/Map.ts @@ -16,6 +16,7 @@ import { Paths } from "./Paths"; import { LandscapeRenderingStrategy, CombinedTilesetRenderingStrategy } from "../landscape/LandscapeRenderingStrategy"; import { IndividualTilesetRenderingStrategy } from "../landscape/IndividualTilesetRenderingStrategy"; +import { ParallaxBackground } from "../landscape/ParallaxBackground"; export class Map { @@ -63,6 +64,9 @@ export class Map { Paths.tileset + "/item/blood/small_stains" ]; + private parallax: ParallaxBackground; + private parallaxImage?: string; + /** Singleton instance. */ private static instance: Map; @@ -86,6 +90,7 @@ export class Map { } else { this.strategy = new CombinedTilesetRenderingStrategy(); } + this.parallax = ParallaxBackground.get(); } /** @@ -122,6 +127,11 @@ export class Map { this.layerGroupIndexes = this.mapLayerGroup(); this.strategy.onMapLoaded(this); + + if (this.parallaxImage) { + this.parallax.setImage(this.parallaxImage, this.zoneSizeX * this.tileWidth, + this.zoneSizeY * this.tileHeight); + } } decodeTileset(content: any, name: string) { @@ -222,4 +232,17 @@ export class Map { hasSafeTileset(filename: string) { return this.knownSafeTilesets.indexOf(filename) > -1; } + + /** + * Sets or unsets parallax image. + * + * @param {string=} name + * Background image filename. + */ + setParallax(name?: string) { + this.parallaxImage = name; + if (typeof(name) === "undefined") { + this.parallax.reset(); + } + } } diff --git a/src/js/stendhal/data/Paths.ts b/src/js/stendhal/data/Paths.ts index 946c63b11e5..294c3736b58 100644 --- a/src/js/stendhal/data/Paths.ts +++ b/src/js/stendhal/data/Paths.ts @@ -24,6 +24,7 @@ export class Paths { public static readonly achievements = Paths.sprites + "/achievements"; public static readonly maps = Paths.data + "/maps"; public static readonly tileset = Paths.maps + "/tileset"; + public static readonly parallax = Paths.maps + "/parallax"; public static readonly ws = Paths.extractPath("data-ws"); /** diff --git a/src/js/stendhal/landscape/ParallaxBackground.ts b/src/js/stendhal/landscape/ParallaxBackground.ts new file mode 100644 index 00000000000..f32472d9434 --- /dev/null +++ b/src/js/stendhal/landscape/ParallaxBackground.ts @@ -0,0 +1,98 @@ +/*************************************************************************** + * Copyright © 2024 - Faiumoni e. V. * + *************************************************************************** + *************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU Affero General Public License as * + * published by the Free Software Foundation; either version 3 of the * + * License, or (at your option) any later version. * + * * + ***************************************************************************/ + +import { singletons } from "../SingletonRepo"; + + +export class ParallaxBackground { + + /** Default scrol rate of parallax background (1/4). */ + public static readonly SCROLL = 0.25; + + /** Tiled image to be drawn. */ + private image?: HTMLImageElement; + + /** Singleton instance. */ + private static instance: ParallaxBackground; + + + /** + * Retrieves singleton instance. + */ + static get(): ParallaxBackground { + if (!ParallaxBackground.instance) { + ParallaxBackground.instance = new ParallaxBackground(); + } + return ParallaxBackground.instance; + } + + /** + * Hidden singleton constructor. + */ + private constructor() { + // do nothing + } + + /** + * Sets image to be drawn. + * + * @param {string} name + * Relative path (exluding .png filename suffix) to image inside "data/maps/parallax" directory + * or `undefined` to unset. + * @param {number} width + * Map pixel width. + * @param {number} height + * Map pixel height. + */ + setImage(name: string, width: number, height: number) { + const fullPath = singletons.getPaths().parallax + "/" + name + ".png"; + this.image = singletons.getSpriteStore().get(fullPath); + + /* FIXME: + //this.image = singletons.getTileStore().getParallax(name, ParallaxBackground.SCROLL, width, height); + singletons.getTileStore().getParallaxPromise(name, ParallaxBackground.SCROLL, width, height) + .then(image => { + this.image = image; + }).catch(error => { + console.error("Error setting parallax background \"" + name + "\"\n", error); + }); + */ + } + + /** + * Unsets parallax background image. + */ + reset() { + this.image = undefined; + } + + draw(ctx: CanvasRenderingContext2D, offsetX: number, offsetY: number) { + if (!this.image || !this.image.height) { + return; + } + + // FIXME: seams are visible when walking + let dy = offsetY - ((offsetY / 4) % this.image.height); + for (dy; dy < this.image.height * 100; dy += this.image.height) { + let dx = offsetX - ((offsetX / 4) % this.image.width); + for (dx; dx < this.image.width * 100; dx += this.image.width) { + ctx.drawImage(this.image, dx, dy); + } + } + + /* TODO: use the following when `setImage` fixed + const tileLeft = offsetX - (offsetX * ParallaxBackground.SCROLL); + const tileTop = offsetY - (offsetY * ParallaxBackground.SCROLL); + ctx.drawImage(this.image, tileLeft, tileTop); + */ + } +} diff --git a/src/js/stendhal/ui/ViewPort.ts b/src/js/stendhal/ui/ViewPort.ts index 85356fcb43e..11179fc93f6 100644 --- a/src/js/stendhal/ui/ViewPort.ts +++ b/src/js/stendhal/ui/ViewPort.ts @@ -144,6 +144,7 @@ export class ViewPort { // FIXME: filter should not be applied to "blend" layers //this.applyFilter(); + stendhal.data.map.parallax.draw(this.ctx, this.offsetX, this.offsetY); stendhal.data.map.strategy.render(this.ctx.canvas, this, tileOffsetX, tileOffsetY, this.targetTileWidth, this.targetTileHeight); this.weatherRenderer.draw(this.ctx);