feat: integrate NPC portrait panel with follow mode in GameScene

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
root
2026-03-07 04:37:42 +00:00
parent ffdfd1474e
commit dfed8401e4
+52
View File
@@ -1,6 +1,8 @@
import Phaser from 'phaser';
import type { SocketClient } from '../network/SocketClient.js';
import { CharacterCompositor } from '../sprites/CharacterCompositor.js';
import { PortraitCompositor } from '../sprites/PortraitCompositor.js';
import { NpcInfoPanel } from '../ui/NpcInfoPanel.js';
import type { WorldState, StateUpdate, EntityState, Appearance } from '@dflike/shared';
import { TILE_SIZE, SPRITE_FRAME_WIDTH, SPRITE_FRAME_HEIGHT, SPRITE_COLS, Direction } from '@dflike/shared';
@@ -21,6 +23,8 @@ export class GameScene extends Phaser.Scene {
private moveThrottle = 0;
private followTargetIndex = 0;
private followThrottle = 0;
private portraitCompositor!: PortraitCompositor;
private npcInfoPanel!: NpcInfoPanel;
constructor() {
super({ key: 'GameScene' });
@@ -33,6 +37,8 @@ export class GameScene extends Phaser.Scene {
create(): void {
this.compositor = new CharacterCompositor(this);
this.portraitCompositor = new PortraitCompositor();
this.npcInfoPanel = new NpcInfoPanel();
// Draw tile grid
this.drawWorld();
@@ -53,6 +59,7 @@ export class GameScene extends Phaser.Scene {
// Tab to toggle mode
this.input.keyboard!.addKey('TAB').on('down', () => {
const prevMode = this.mode;
if (this.mode === 'camera') {
this.mode = 'avatar';
} else if (this.mode === 'avatar') {
@@ -62,6 +69,13 @@ export class GameScene extends Phaser.Scene {
}
this.client.sendInput({ type: 'toggle-mode' });
this.updateModeText();
// Panel visibility
if (this.mode === 'follow') {
this.showFollowPanel();
} else if (prevMode === 'follow') {
this.npcInfoPanel.hide();
}
});
// UI text
@@ -221,6 +235,19 @@ export class GameScene extends Phaser.Scene {
this.entitySprites.delete(id);
}
}
// Live-update the follow panel if visible
if (this.mode === 'follow' && this.npcInfoPanel.isVisible()) {
const npcIds = this.getNpcIds();
const followedId = npcIds[this.followTargetIndex];
const followed = this.entitySprites.get(followedId);
if (followed) {
this.npcInfoPanel.updateInfo(followed.lastState);
if (followed.lastState.needs) {
this.npcInfoPanel.updateNeeds(followed.lastState.needs);
}
}
}
}
update(_time: number, delta: number): void {
@@ -285,10 +312,12 @@ export class GameScene extends Phaser.Scene {
this.followTargetIndex = (this.followTargetIndex - 1 + npcIds.length) % npcIds.length;
this.followThrottle = 150;
this.updateModeText();
this.updateFollowPanel();
} else if (right) {
this.followTargetIndex = (this.followTargetIndex + 1) % npcIds.length;
this.followThrottle = 150;
this.updateModeText();
this.updateFollowPanel();
}
}
@@ -301,6 +330,29 @@ export class GameScene extends Phaser.Scene {
}
}
private async showFollowPanel(): Promise<void> {
const npcIds = this.getNpcIds();
if (npcIds.length === 0) return;
const targetId = npcIds[this.followTargetIndex];
const es = this.entitySprites.get(targetId);
if (!es) return;
const portraitUrl = await this.portraitCompositor.compositePortrait(es.lastState.appearance);
this.npcInfoPanel.show(es.lastState, portraitUrl);
}
private async updateFollowPanel(): Promise<void> {
const npcIds = this.getNpcIds();
if (npcIds.length === 0) return;
const targetId = npcIds[this.followTargetIndex];
const es = this.entitySprites.get(targetId);
if (!es) return;
const portraitUrl = await this.portraitCompositor.compositePortrait(es.lastState.appearance);
this.npcInfoPanel.updatePortrait(portraitUrl);
this.npcInfoPanel.updateInfo(es.lastState);
}
private getNpcIds(): number[] {
return [...this.entitySprites.keys()].sort((a, b) => a - b);
}