Back to Browser Side Quest
SOURCE / PINNED RELEASE

Made of little things.

Browser Side Quest

Release
b9d42f5768e4…
Author-recorded commit
c267690145fa…
License
LICENSE
Author’s source reference
nostr://npub1ye5ptcxfyyxl5vjvdjar2ua3f0hynkjzpx552mu5snj3qmx5pzjscpknpr/wss%3A%2F%2Fgit.shakespeare.diy%2F/browser-side-quest

Archive hash verified: 6d4b90b8a5f328f7…. The source-to-build association is the author’s claim; it has not been independently rebuilt.

src/client/timing.ts
// Port of BrowserQuest client/js/{timer,animation,transition,tile}.js (MPL-2.0).

export class Timer {
  lastTime: number;

  constructor(
    public duration: number,
    startTime = 0,
  ) {
    this.lastTime = startTime;
  }

  isOver(time: number): boolean {
    if (time - this.lastTime > this.duration) {
      this.lastTime = time;
      return true;
    }
    return false;
  }
}

export interface Frame {
  index: number;
  x: number;
  y: number;
}

export class Animation {
  lastTime = 0;
  speed = 0;
  count = 0;
  currentFrame: Frame = { index: 0, x: 0, y: 0 };
  private endcountCallback: () => void = () => {};

  constructor(
    readonly name: string,
    readonly length: number,
    readonly row: number,
    readonly width: number,
    readonly height: number,
  ) {
    this.reset();
  }

  tick(): void {
    let i = this.currentFrame.index;
    i = i < this.length - 1 ? i + 1 : 0;

    if (this.count > 0 && i === 0) {
      this.count -= 1;
      if (this.count === 0) {
        this.currentFrame.index = 0;
        this.endcountCallback();
        return;
      }
    }

    this.currentFrame.x = this.width * i;
    this.currentFrame.y = this.height * this.row;
    this.currentFrame.index = i;
  }

  setSpeed(speed: number): void {
    this.speed = speed;
  }

  setCount(count: number, onEndCount: () => void): void {
    this.count = count;
    this.endcountCallback = onEndCount;
  }

  isTimeToAnimate(time: number): boolean {
    return time - this.lastTime > this.speed;
  }

  update(time: number): boolean {
    if (this.lastTime === 0 && this.name.substring(0, 3) === 'atk') {
      this.lastTime = time;
    }
    if (this.isTimeToAnimate(time)) {
      this.lastTime = time;
      this.tick();
      return true;
    }
    return false;
  }

  reset(): void {
    this.lastTime = 0;
    this.currentFrame = { index: 0, x: 0, y: this.row * this.height };
  }
}

export class Transition {
  startValue = 0;
  endValue = 0;
  duration = 0;
  inProgress = false;
  startTime = 0;
  count = 0;
  private updateFunction: ((value: number) => void) | null = null;
  private stopFunction: (() => void) | null = null;

  start(
    currentTime: number,
    updateFunction: ((value: number) => void) | null,
    stopFunction: (() => void) | null,
    startValue: number,
    endValue: number,
    duration: number,
  ): void {
    this.startTime = currentTime;
    this.updateFunction = updateFunction;
    this.stopFunction = stopFunction;
    this.startValue = startValue;
    this.endValue = endValue;
    this.duration = duration;
    this.inProgress = true;
    this.count = 0;
  }

  step(currentTime: number): void {
    if (!this.inProgress) return;
    if (this.count > 0) {
      this.count -= 1;
      return;
    }
    let elapsed = currentTime - this.startTime;
    if (elapsed > this.duration) elapsed = this.duration;

    const diff = this.endValue - this.startValue;
    const i = Math.round(this.startValue + (diff / this.duration) * elapsed);

    if (elapsed === this.duration || i === this.endValue) {
      this.stop();
      this.stopFunction?.();
    } else {
      this.updateFunction?.(i);
    }
  }

  restart(currentTime: number, startValue: number, endValue: number): void {
    this.start(currentTime, this.updateFunction, this.stopFunction, startValue, endValue, this.duration);
    this.step(currentTime);
  }

  stop(): void {
    this.inProgress = false;
  }
}

export class AnimatedTile {
  readonly startId: number;
  lastTime = 0;
  x = 0;
  y = 0;
  isDirty = false;

  constructor(
    public id: number,
    readonly length: number,
    readonly speed: number,
    readonly index: number,
  ) {
    this.startId = id;
  }

  tick(): void {
    if (this.id - this.startId < this.length - 1) this.id += 1;
    else this.id = this.startId;
  }

  animate(time: number): boolean {
    if (time - this.lastTime > this.speed) {
      this.tick();
      this.lastTime = time;
      return true;
    }
    return false;
  }
}

export class Area {
  id: number | string = 0;
  musicName = '';

  constructor(
    readonly x: number,
    readonly y: number,
    readonly width: number,
    readonly height: number,
  ) {}

  contains(entity: { gridX: number; gridY: number } | null): boolean {
    if (!entity) return false;
    return (
      entity.gridX >= this.x &&
      entity.gridY >= this.y &&
      entity.gridX < this.x + this.width &&
      entity.gridY < this.y + this.height
    );
  }
}