diff --git a/buildtools/ant_modules/dist.xml b/buildtools/ant_modules/dist.xml index 1803f5f043a..eeb5987e893 100644 --- a/buildtools/ant_modules/dist.xml +++ b/buildtools/ant_modules/dist.xml @@ -132,6 +132,10 @@ + + + + diff --git a/src/js/stendhal/Client.ts b/src/js/stendhal/Client.ts index 6f911211867..3654a45cbc3 100644 --- a/src/js/stendhal/Client.ts +++ b/src/js/stendhal/Client.ts @@ -485,7 +485,9 @@ export class Client { const musicVolume = parseFloat(zoneinfo["music_volume"]); stendhal.ui.soundMan.playSingleGlobalizedMusic(zoneinfo["music"], !Number.isNaN(musicVolume) ? musicVolume : 1.0); - + // parallax background + stendhal.data.map.parallax.setImage(zoneinfo["parallax"]); + // zone coloring if (zoneinfo["color"]) { const hsl = Color.hexToHSL(Color.numToHex(Number(zoneinfo["color"]))); // workaround until able to get right saturation level from color methods diff --git a/src/js/stendhal/data/Map.ts b/src/js/stendhal/data/Map.ts index 2e493b768dd..951ca6a81ae 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,8 @@ export class Map { Paths.tileset + "/item/blood/small_stains" ]; + private parallax: ParallaxBackground; + /** Singleton instance. */ private static instance: Map; @@ -86,6 +89,7 @@ export class Map { } else { this.strategy = new CombinedTilesetRenderingStrategy(); } + this.parallax = ParallaxBackground.get(); } /** diff --git a/src/js/stendhal/data/Paths.ts b/src/js/stendhal/data/Paths.ts index 15931181eb9..28eeb9aec65 100644 --- a/src/js/stendhal/data/Paths.ts +++ b/src/js/stendhal/data/Paths.ts @@ -23,6 +23,7 @@ export class Paths { public static readonly weather = Paths.sprites + "/weather"; public static readonly achievements = Paths.sprites + "/achievements"; public static readonly tileset = Paths.data + "/maps/tileset"; + public static readonly parallax = Paths.data + "/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..766badd8cab --- /dev/null +++ b/src/js/stendhal/landscape/ParallaxBackground.ts @@ -0,0 +1,77 @@ +/*************************************************************************** + * 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 { + + /** 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 name {string} + * Relative path (exluding .png filename suffix) to image inside "data/maps/parallax" directory + * or `undefined` to unset. + */ + public setImage(name?: string) { + if (!name) { + this.image = undefined; + return; + } + const fullPath = singletons.getPaths().parallax + "/" + name + ".png"; + this.image = singletons.getSpriteStore().get(fullPath); + } + + draw(ctx: CanvasRenderingContext2D, offsetX: number, offsetY: number) { + if (!this.image || !this.image.height) { + return; + } + // FIXME: jumps when shifting tiles + const tilesX = Math.max(Math.ceil(ctx.canvas.width / this.image.width) + 1, 1); + const tilesY = Math.max(Math.ceil(ctx.canvas.height / this.image.height) + 1, 1); + const clipLeft = (offsetX / 2) % ctx.canvas.width; + const clipTop = (offsetY / 2) % ctx.canvas.height; + for (let ix = 0; ix < tilesX; ix++) { + for (let iy = 0; iy < tilesY; iy++) { + ctx.drawImage(this.image, + 0, 0, this.image.width, this.image.height, + (ix*this.image.width)+offsetX-clipLeft, + (iy*this.image.height)+offsetY-clipTop, + this.image.width, this.image.height); + } + } + } +} diff --git a/src/js/stendhal/ui/ViewPort.ts b/src/js/stendhal/ui/ViewPort.ts index 440a770ad9d..3f00a7aff68 100644 --- a/src/js/stendhal/ui/ViewPort.ts +++ b/src/js/stendhal/ui/ViewPort.ts @@ -149,6 +149,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);