diff --git a/resources/sprites/atombomb.png b/resources/sprites/atombomb.png index 353fbadeb..cec7cd0c3 100644 Binary files a/resources/sprites/atombomb.png and b/resources/sprites/atombomb.png differ diff --git a/resources/sprites/christmas/happy_elf.png b/resources/sprites/christmas/happy_elf.png new file mode 100644 index 000000000..636a97613 Binary files /dev/null and b/resources/sprites/christmas/happy_elf.png differ diff --git a/resources/sprites/christmas/sad_elf.png b/resources/sprites/christmas/sad_elf.png new file mode 100644 index 000000000..26e36e6b6 Binary files /dev/null and b/resources/sprites/christmas/sad_elf.png differ diff --git a/resources/sprites/christmas/santa.png b/resources/sprites/christmas/santa.png new file mode 100644 index 000000000..13f1669c6 Binary files /dev/null and b/resources/sprites/christmas/santa.png differ diff --git a/resources/sprites/christmas/snowman.png b/resources/sprites/christmas/snowman.png new file mode 100644 index 000000000..00a3df1d1 Binary files /dev/null and b/resources/sprites/christmas/snowman.png differ diff --git a/resources/sprites/christmas/sparks.png b/resources/sprites/christmas/sparks.png new file mode 100644 index 000000000..f5be5ab28 Binary files /dev/null and b/resources/sprites/christmas/sparks.png differ diff --git a/resources/sprites/hydrogenbomb.png b/resources/sprites/hydrogenbomb.png index fd3c0de34..683e9f9d1 100644 Binary files a/resources/sprites/hydrogenbomb.png and b/resources/sprites/hydrogenbomb.png differ diff --git a/resources/sprites/mirv2.png b/resources/sprites/mirv2.png index 78f1a220f..5e509e15e 100644 Binary files a/resources/sprites/mirv2.png and b/resources/sprites/mirv2.png differ diff --git a/src/client/graphics/AnimatedSpriteLoader.ts b/src/client/graphics/AnimatedSpriteLoader.ts index 03e49e0cc..2f4ce53eb 100644 --- a/src/client/graphics/AnimatedSpriteLoader.ts +++ b/src/client/graphics/AnimatedSpriteLoader.ts @@ -1,5 +1,10 @@ import miniBigSmoke from "../../../resources/sprites/bigsmoke.png"; import buildingExplosion from "../../../resources/sprites/buildingExplosion.png"; +import happyElf from "../../../resources/sprites/christmas/happy_elf.png"; +import sadElf from "../../../resources/sprites/christmas/sad_elf.png"; +import santa from "../../../resources/sprites/christmas/santa.png"; +import snowman from "../../../resources/sprites/christmas/snowman.png"; +import sparks from "../../../resources/sprites/christmas/sparks.png"; import conquestSword from "../../../resources/sprites/conquestSword.png"; import dust from "../../../resources/sprites/dust.png"; import miniExplosion from "../../../resources/sprites/miniExplosion.png"; @@ -135,6 +140,51 @@ const ANIMATED_SPRITE_CONFIG: Partial> = { originX: 10, originY: 16, }, + [FxType.Santa]: { + url: santa, + frameWidth: 34, + frameCount: 8, + frameDuration: 90, + looping: true, + originX: 16, + originY: 15, + }, + [FxType.Snowman]: { + url: snowman, + frameWidth: 16, + frameCount: 19, + frameDuration: 200, + looping: false, + originX: 8, + originY: 12, + }, + [FxType.HappyElf]: { + url: happyElf, + frameWidth: 7, + frameCount: 5, + frameDuration: 90, + looping: true, + originX: 3, + originY: 7, + }, + [FxType.SadElf]: { + url: sadElf, + frameWidth: 14, + frameCount: 10, + frameDuration: 90, + looping: true, + originX: 6, + originY: 10, + }, + [FxType.Sparks]: { + url: sparks, + frameWidth: 13, + frameCount: 13, + frameDuration: 60, + looping: false, + originX: 6, + originY: 6, + }, }; export class AnimatedSpriteLoader { diff --git a/src/client/graphics/fx/Fx.ts b/src/client/graphics/fx/Fx.ts index d4b206614..842514b78 100644 --- a/src/client/graphics/fx/Fx.ts +++ b/src/client/graphics/fx/Fx.ts @@ -16,4 +16,9 @@ export enum FxType { UnderConstruction = "UnderConstruction", Dust = "Dust", Conquest = "Conquest", + Santa = "Santa", + Snowman = "Snowman", + HappyElf = "HappyElf", + SadElf = "SadElf", + Sparks = "Sparks", } diff --git a/src/client/graphics/fx/NukeFx.ts b/src/client/graphics/fx/NukeFx.ts index 479d68e18..79f70c2cd 100644 --- a/src/client/graphics/fx/NukeFx.ts +++ b/src/client/graphics/fx/NukeFx.ts @@ -88,10 +88,10 @@ export function nukeFxFactory( radiusFactor: number; density: number; }> = [ - { type: FxType.MiniFire, radiusFactor: 1.0, density: 1 / 25 }, - { type: FxType.MiniSmoke, radiusFactor: 1.0, density: 1 / 28 }, + { type: FxType.HappyElf, radiusFactor: 1.0, density: 1 / 25 }, + { type: FxType.SadElf, radiusFactor: 1.0, density: 1 / 28 }, { type: FxType.MiniBigSmoke, radiusFactor: 0.9, density: 1 / 70 }, - { type: FxType.MiniSmokeAndFire, radiusFactor: 0.9, density: 1 / 70 }, + { type: FxType.Snowman, radiusFactor: 0.9, density: 1 / 70 }, ]; for (const { type, radiusFactor, density } of debrisPlan) { diff --git a/src/client/graphics/fx/SantaFx.ts b/src/client/graphics/fx/SantaFx.ts new file mode 100644 index 000000000..88c7034d9 --- /dev/null +++ b/src/client/graphics/fx/SantaFx.ts @@ -0,0 +1,46 @@ +import { Theme } from "../../../core/configuration/Config"; +import { PlayerView } from "../../../core/game/GameView"; +import { AnimatedSpriteLoader } from "../AnimatedSpriteLoader"; +import { Fx, FxType } from "./Fx"; +import { SpriteFx } from "./SpriteFx"; + +export class SantaFx implements Fx { + private spriteFx: SpriteFx; + private speed: number = 0.05; // px / ms + + constructor( + animatedSpriteLoader: AnimatedSpriteLoader, + private startX: number, + private startY: number, + private endX: number, + owner?: PlayerView, + theme?: Theme, + ) { + const distance = Math.abs(endX - startX); + const duration = Math.max(distance / this.speed, 1); + + this.spriteFx = new SpriteFx( + animatedSpriteLoader, + startX, + startY, + FxType.Santa, + duration, + owner, + theme, + ); + } + + renderTick(frameTime: number, ctx: CanvasRenderingContext2D): boolean { + const elapsed = this.spriteFx.getElapsedTime(); + const duration = this.spriteFx.getDuration(); + + const t = elapsed / duration; + if (t >= 1) return false; + + const x = this.startX + Math.floor((this.endX - this.startX) * t); + const y = this.startY; + this.spriteFx.setPosition(x, y); + + return this.spriteFx.renderTick(frameTime, ctx); + } +} diff --git a/src/client/graphics/fx/SpriteFx.ts b/src/client/graphics/fx/SpriteFx.ts index 2926121a8..24818da7c 100644 --- a/src/client/graphics/fx/SpriteFx.ts +++ b/src/client/graphics/fx/SpriteFx.ts @@ -69,6 +69,11 @@ export class SpriteFx implements Fx { } } + public setPosition(x: number, y: number): void { + this.x = x; + this.y = y; + } + renderTick(frameTime: number, ctx: CanvasRenderingContext2D): boolean { if (!this.animatedSprite) return false; diff --git a/src/client/graphics/layers/FxLayer.ts b/src/client/graphics/layers/FxLayer.ts index a2e0990a2..6b7aaf790 100644 --- a/src/client/graphics/layers/FxLayer.ts +++ b/src/client/graphics/layers/FxLayer.ts @@ -14,6 +14,7 @@ import { conquestFxFactory } from "../fx/ConquestFx"; import { Fx, FxType } from "../fx/Fx"; import { NukeAreaFx } from "../fx/NukeAreaFx"; import { nukeFxFactory, ShockwaveFx } from "../fx/NukeFx"; +import { SantaFx } from "../fx/SantaFx"; import { SpriteFx } from "../fx/SpriteFx"; import { TargetFx } from "../fx/TargetFx"; import { TextFx } from "../fx/TextFx"; @@ -33,6 +34,9 @@ export class FxLayer implements Layer { private boatTargetFxByUnitId: Map = new Map(); private nukeTargetFxByUnitId: Map = new Map(); + private lastSantaTick = 0; + private santaIntervalTicks = 60 * 10; // one each minute + constructor(private game: GameView) { this.theme = this.game.config().theme(); } @@ -43,6 +47,7 @@ export class FxLayer implements Layer { tick() { this.manageBoatTargetFx(); + this.spawnSantaIfNeeded(); this.game .updatesSinceLastTick() ?.[GameUpdateType.Unit]?.map((unit) => this.game.unit(unit.id)) @@ -71,6 +76,24 @@ export class FxLayer implements Layer { }); } + private spawnSantaIfNeeded() { + const currentTick = this.game.ticks(); + if (currentTick - this.lastSantaTick < this.santaIntervalTicks) { + return; + } + this.lastSantaTick = currentTick; + // Santa enters left side, exits right + const margin = 50; + const startX = -margin; + const endX = this.game.width() + margin; + const startY = Math.floor( + margin + Math.random() * (this.game.height() - 2 * margin), + ); + const santa = new SantaFx(this.animatedSpriteLoader, startX, startY, endX); + + this.allFx.push(santa); + } + private manageBoatTargetFx() { // End markers for boats that arrived or retreated for (const [unitId, fx] of Array.from( @@ -168,6 +191,9 @@ export class FxLayer implements Layer { this.onNukeEvent(unit, 70); break; } + case UnitType.MIRV: + this.addSparks(unit); + break; case UnitType.MIRVWarhead: this.onNukeEvent(unit, 70); break; @@ -302,6 +328,20 @@ export class FxLayer implements Layer { } } + addSparks(unit: UnitView) { + if (unit.isActive()) { + const x = this.game.x(unit.lastTile()); + const y = this.game.y(unit.lastTile()); + const sparks = new SpriteFx( + this.animatedSpriteLoader, + x, + y, + FxType.Sparks, + ); + this.allFx.push(sparks); + } + } + onNukeEvent(unit: UnitView, radius: number) { if (!unit.isActive()) { const fx = this.nukeTargetFxByUnitId.get(unit.id());