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.
// Port of BrowserQuest client/js/updater.js (MPL-2.0).
import { Orientations } from '../shared/gametypes.js';
import { Character, type Entity } from './entity.js';
import type { Game } from './game.js';
import { Timer } from './timing.js';
export class Updater {
private readonly playerAggroTimer = new Timer(1000);
constructor(private readonly game: Game) {}
update(): void {
this.updateZoning();
this.updateCharacters();
this.updatePlayerAggro();
this.updateTransitions();
this.updateAnimations();
this.updateAnimatedTiles();
this.updateChatBubbles();
this.updateInfos();
}
private updateCharacters(): void {
this.game.forEachEntity((entity) => {
if (entity.isLoaded) {
if (entity instanceof Character) {
this.updateCharacter(entity);
this.game.onCharacterUpdate(entity);
}
this.updateEntityFading(entity);
}
});
}
private updatePlayerAggro(): void {
const t = this.game.currentTime;
const player = this.game.player;
// Check player aggro every 1s when not moving nor attacking
if (player && !player.isMoving() && !player.isAttacking() && this.playerAggroTimer.isOver(t)) {
player.checkAggro();
}
}
private updateEntityFading(entity: Entity): void {
if (entity.isFading) {
const duration = 1000;
const dt = this.game.currentTime - entity.startFadingTime;
if (dt > duration) {
entity.isFading = false;
entity.fadingAlpha = 1;
} else {
entity.fadingAlpha = dt / duration;
}
}
}
private updateTransitions(): void {
this.game.forEachEntity((entity) => {
if (entity instanceof Character && entity.movement.inProgress) {
entity.movement.step(this.game.currentTime);
}
});
const z = this.game.currentZoning;
if (z && z.inProgress) z.step(this.game.currentTime);
}
private updateZoning(): void {
const g = this.game;
const c = g.camera;
const z = g.currentZoning;
const ts = 16;
const speed = 500;
if (z && z.inProgress === false) {
const orientation = g.zoningOrientation;
let startValue = 0;
let endValue = 0;
let updateFunc: ((v: number) => void) | null = null;
let endFunc: (() => void) | null = null;
if (orientation === Orientations.LEFT || orientation === Orientations.RIGHT) {
const offset = (c.gridW - 2) * ts;
startValue = orientation === Orientations.LEFT ? c.x - ts : c.x + ts;
endValue = orientation === Orientations.LEFT ? c.x - offset : c.x + offset;
updateFunc = (x) => {
c.setPosition(x, c.y);
g.initAnimatedTiles();
g.renderer.renderStaticCanvases();
};
endFunc = () => {
c.setPosition(z.endValue, c.y);
g.endZoning();
};
} else if (orientation === Orientations.UP || orientation === Orientations.DOWN) {
const offset = (c.gridH - 2) * ts;
startValue = orientation === Orientations.UP ? c.y - ts : c.y + ts;
endValue = orientation === Orientations.UP ? c.y - offset : c.y + offset;
updateFunc = (y) => {
c.setPosition(c.x, y);
g.initAnimatedTiles();
g.renderer.renderStaticCanvases();
};
endFunc = () => {
c.setPosition(c.x, z.endValue);
g.endZoning();
};
}
z.start(g.currentTime, updateFunc, endFunc, startValue, endValue, speed);
}
}
private updateCharacter(c: Character): void {
// Estimate of the movement distance for one update
const tick = Math.round(16 / Math.round(c.moveSpeed / (1000 / this.game.renderer.FPS)));
if (c.isMoving() && c.movement.inProgress === false) {
const t = this.game.currentTime;
switch (c.orientation) {
case Orientations.LEFT:
c.movement.start(t, (x) => { c.x = x; c.hasMoved(); },
() => { c.x = c.movement.endValue; c.hasMoved(); c.nextStep(); },
c.x - tick, c.x - 16, c.moveSpeed);
break;
case Orientations.RIGHT:
c.movement.start(t, (x) => { c.x = x; c.hasMoved(); },
() => { c.x = c.movement.endValue; c.hasMoved(); c.nextStep(); },
c.x + tick, c.x + 16, c.moveSpeed);
break;
case Orientations.UP:
c.movement.start(t, (y) => { c.y = y; c.hasMoved(); },
() => { c.y = c.movement.endValue; c.hasMoved(); c.nextStep(); },
c.y - tick, c.y - 16, c.moveSpeed);
break;
case Orientations.DOWN:
c.movement.start(t, (y) => { c.y = y; c.hasMoved(); },
() => { c.y = c.movement.endValue; c.hasMoved(); c.nextStep(); },
c.y + tick, c.y + 16, c.moveSpeed);
break;
}
}
}
private updateAnimations(): void {
const t = this.game.currentTime;
this.game.forEachEntity((entity) => {
if (entity.currentAnimation?.update(t)) entity.setDirty();
});
this.game.sparksAnimation?.update(t);
this.game.targetAnimation?.update(t);
}
updateAnimatedTiles(): void {
const t = this.game.currentTime;
this.game.forEachAnimatedTile((tile) => {
if (tile.animate(t)) tile.isDirty = true;
});
}
private updateChatBubbles(): void {
this.game.bubbleManager.update(this.game.currentTime);
}
private updateInfos(): void {
this.game.infoManager.update(this.game.currentTime);
}
}
