diff --git a/README.md b/README.md index b13768a..d9f8be6 100644 --- a/README.md +++ b/README.md @@ -72,3 +72,4 @@ Check `package.json`, there are also commands available to run tests and check t - [Ice](https://freesound.org/people/kyles/sounds/452252/): kyles - [Rumble](https://freesound.org/people/prozaciswack/sounds/82722/): prozaciswack - [Drain](https://freesound.org/people/Incarnadine/sounds/17128/): Incarnadine +- [Burn](https://freesound.org/people/magnuswaker/sounds/581078/): magnuswaker diff --git a/public/atlas/spells/wheel_1.png b/public/atlas/spells/wheel_1.png new file mode 100644 index 0000000..c1b7f16 Binary files /dev/null and b/public/atlas/spells/wheel_1.png differ diff --git a/public/atlas/spells/wheel_2.png b/public/atlas/spells/wheel_2.png new file mode 100644 index 0000000..a840779 Binary files /dev/null and b/public/atlas/spells/wheel_2.png differ diff --git a/public/atlas/spells/wheel_3.png b/public/atlas/spells/wheel_3.png new file mode 100644 index 0000000..d200e57 Binary files /dev/null and b/public/atlas/spells/wheel_3.png differ diff --git a/public/atlas/spells/wheel_4.png b/public/atlas/spells/wheel_4.png new file mode 100644 index 0000000..db2d0b0 Binary files /dev/null and b/public/atlas/spells/wheel_4.png differ diff --git a/public/sound/581078__magnuswaker__flame-loop.mp3 b/public/sound/581078__magnuswaker__flame-loop.mp3 new file mode 100644 index 0000000..4ff9ad1 Binary files /dev/null and b/public/sound/581078__magnuswaker__flame-loop.mp3 differ diff --git a/src/data/entity/index.ts b/src/data/entity/index.ts index 6be3075..c332aef 100644 --- a/src/data/entity/index.ts +++ b/src/data/entity/index.ts @@ -2,6 +2,7 @@ import { Bakuretsu } from "../spells/bakuretsu"; import { Bomb } from "../spells/bomb"; import { Catastravia } from "../spells/catastravia"; import { CatastraviaMissile } from "../spells/catastraviaMissile"; +import { FireWheel } from "../spells/fireWheel"; import { Fireball } from "../spells/fireball"; import { GateOfBabylon } from "../spells/gateOfBabylon"; import { Hairpin } from "../spells/hairpin"; @@ -56,6 +57,7 @@ export const ENTITIES: Record< [EntityType.Excalibur]: Sword, [EntityType.Bakuretsu]: Bakuretsu, [EntityType.WindBlast]: WindBlast, + [EntityType.FireWheel]: FireWheel, }; interface SpawnRateData { diff --git a/src/data/entity/types.ts b/src/data/entity/types.ts index 2237cc3..c7e8ddf 100644 --- a/src/data/entity/types.ts +++ b/src/data/entity/types.ts @@ -86,6 +86,7 @@ export enum EntityType { Excalibur, Bakuretsu, WindBlast, + FireWheel, } export enum Priority { diff --git a/src/data/spells/fireWheel.ts b/src/data/spells/fireWheel.ts new file mode 100644 index 0000000..d0da879 --- /dev/null +++ b/src/data/spells/fireWheel.ts @@ -0,0 +1,193 @@ +import { AnimatedSprite, Container } from "pixi.js"; +import { Level } from "../map/level"; +import { AssetsContainer } from "../../util/assets/assetsContainer"; + +import { circle16x16 } from "../collision/precomputed/circles"; +import { ExplosiveDamage } from "../damage/explosiveDamage"; +import { Character } from "../entity/character"; + +import { Explosion } from "../../graphics/explosion"; +import { Manager } from "../network/manager"; +import { TurnState } from "../network/types"; +import { EntityType, Layer, Priority, Syncable } from "../entity/types"; +import { Server } from "../network/server"; +import { Element } from "./types"; + +import { StickyBody } from "../collision/stickyBody"; +import { ControllableSound } from "../../sound/controllableSound"; +import { Sound } from "../../sound"; + +export class FireWheel extends Container implements Syncable { + public readonly body: StickyBody; + private sprite: AnimatedSprite; + private lifetime = 500; + private sound?: ControllableSound; + + public id = -1; + public readonly type = EntityType.FireWheel; + public readonly priority = Priority.High; + public layer = Layer.Background; + + constructor(x: number, y: number, speed: number, private direction: number) { + super(); + + this.body = new StickyBody(Level.instance.terrain.collisionMask, { + mask: circle16x16, + velocity: 2, + }); + this.body.move(x, y); + this.body.addAngularVelocity(speed, direction); + this.position.set(x * 6, y * 6); + this.sound = ControllableSound.fromEntity(this, Sound.Fire); + + const atlas = AssetsContainer.instance.assets!["atlas"]; + + this.sprite = new AnimatedSprite(atlas.animations["spells_wheel"]); + this.sprite.animationSpeed = 0.1; + this.sprite.scale.set(3); + this.sprite.play(); + this.sprite.anchor.set(0.5); + this.sprite.position.set(3); + + // const canvas = new OffscreenCanvas(1, 1); + // const ctx = canvas.getContext("2d")!; + + // ctx.fillStyle = "#000000"; + // ctx.fillRect(0, 0, 1, 1); + + // const sprite = new Sprite(Texture.from(canvas)); + // sprite.scale.set(6); + + // const sprite2 = new Sprite( + // Texture.fromBuffer(circle16x16Canvas.data, 16, 16) + // ); + // sprite2.anchor.set(0); + // sprite2.scale.set(6); + // sprite2.alpha = 0.5; + // sprite2.position.set(-48); + + this.addChild(this.sprite); + + // When spawning in a wall + if ( + Server.instance && + Level.instance.terrain.collisionMask.collidesWithPoint( + ...this.body.position + ) + ) { + this._die(x, y); + } + } + + private _die(x: number, y: number) { + Level.instance.damage( + new ExplosiveDamage( + x, + y, + 16, + 3, + 2 + Manager.instance.getElementValue(Element.Elemental) + ) + ); + Server.instance.kill(this); + } + + die() { + Level.instance.remove(this); + new Explosion(this.position.x, this.position.y); + Manager.instance.setTurnState(TurnState.Ending); + this.sound?.destroy(); + } + + getCenter(): [number, number] { + return [this.position.x, this.position.y]; + } + + tick(dt: number) { + this.body.tick(dt); + this.sprite.scale.x = this.body.direction * 3; + const [x, y] = this.body.precisePosition; + this.position.set(x * 6, y * 6); + + if (this.body.sticky) { + if (this.sound?.alias !== Sound.Burn) { + this.sound?.destroy(); + this.sound = undefined; + } + + if (!this.sound) { + this.sound = ControllableSound.fromEntity(this, Sound.Burn, { + loop: true, + }); + } else { + this.sound.update(this); + } + } + + if (!Server.instance) { + return; + } + + this.lifetime -= dt; + if ( + this.lifetime <= 0 || + Level.instance.terrain.killbox.collidesWith( + this.body.mask, + this.position.x - 48, + this.position.y - 48 + ) + ) { + this._die(x, y); + return; + } + + Level.instance.withNearbyEntities( + this.position.x, + this.position.y, + 10 * 6, + (entity) => { + if (entity instanceof Character) { + this._die(x, y); + return true; + } + } + ); + } + + serialize() { + return this.body.serialize(); + } + + deserialize(data: ReturnType) { + this.body.deserialize(data); + } + + serializeCreate() { + return [ + ...this.body.precisePosition, + this.body.velocity, + this.direction, + ] as const; + } + + static create(data: ReturnType) { + return new FireWheel(...data); + } + + static cast( + x: number, + y: number, + character: Character, + power: number, + direction: number + ) { + if (!Server.instance) { + return; + } + + const entity = new FireWheel(x, y, power * 1.5, direction); + + Server.instance.create(entity); + return entity; + } +} diff --git a/src/data/spells/index.ts b/src/data/spells/index.ts index d766547..7117edf 100644 --- a/src/data/spells/index.ts +++ b/src/data/spells/index.ts @@ -28,6 +28,7 @@ import { Hairpin } from "./hairpin"; import { IceWallSpawner } from "./iceWallSpawner"; import { Rock } from "./rock"; import { Meteor } from "./meteor"; +import { FireWheel } from "./fireWheel"; export interface Spell { name: string; @@ -331,6 +332,21 @@ const METEOR = spell(Lock, { }, }); +const FIRE_WHEEL = spell(PoweredArcaneCircle, { + name: "Flame wheel", + description: "", + elements: [Element.Elemental], + cost: 20, + data: { + projectile: FireWheel, + xOffset: 14, + yOffset: 17.5, + x: 3, + y: 6.5, + turnState: TurnState.Attacked, + }, +}); + export const SPELLS: Spell[] = [ MELEE, FIREBALL, @@ -351,4 +367,5 @@ export const SPELLS: Spell[] = [ ICE_WALL, ROCK, METEOR, + FIRE_WHEEL, ]; diff --git a/src/sound/controllableSound.ts b/src/sound/controllableSound.ts index 1967e23..660dddd 100644 --- a/src/sound/controllableSound.ts +++ b/src/sound/controllableSound.ts @@ -78,7 +78,7 @@ class ControllableSound { } constructor( - private alias: Sound, + public readonly alias: Sound, private filter: filters.StereoFilter, options: PlayOptions ) { @@ -116,7 +116,7 @@ class ControllableSound { ); } - update(entity: HurtableEntity | [number, number]) { + update(entity: Spawnable | [number, number]) { if (this.ref) { const { volume, pan } = ControllableSound.getSoundOptions( ...("getCenter" in entity ? entity.getCenter() : entity) diff --git a/src/sound/index.ts b/src/sound/index.ts index e70cb59..4c376c9 100644 --- a/src/sound/index.ts +++ b/src/sound/index.ts @@ -34,6 +34,7 @@ export enum Sound { Ice = "iceSnd", Rumble = "rumbleSnd", Drain = "drainSnd", + Burn = "burnSnd", } interface SoundData { @@ -113,6 +114,7 @@ addSoundData(Sound.Ice, "452252__kyles__ice-cracks-medium7-brittle_2"); addSoundData(Sound.Ice, "452252__kyles__ice-cracks-medium7-brittle_3"); addSoundData(Sound.Rumble, "82722__prozaciswack__digging"); addSoundData(Sound.Drain, "17128__incarnadine__water_go_down_the_hole"); +addSoundData(Sound.Burn, "581078__magnuswaker__flame-loop"); export const SOUND_ASSETS = Object.fromEntries( Object.values(SOUND_DATA).reduce(