Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 31 additions & 11 deletions packages/client/src/components/pretext/game/BattleSceneCanvas.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -130,15 +130,18 @@ const playerTemplateCache = new Map<
function buildPlayerTemplate(race: Race) {
const url = RACE_GLB_URL[race];
if (!url) return null;
// Dwarf is stocky (wider, shorter), Elf is lean (narrower, taller), Human is balanced
const gridW = race === Race.Dwarf ? 7 : race === Race.Elf ? 6 : 7;
const gridH = race === Race.Dwarf ? 6 : race === Race.Elf ? 8 : 7;
return {
id: `player-${Race[race]?.toLowerCase() ?? 'unknown'}`,
name: Race[race] ?? 'Adventurer',
gridWidth: 7,
gridHeight: 7,
gridWidth: gridW,
gridHeight: gridH,
monsterClass: 0 as const,
level: 1,
dynamic: true,
draw: makeGLBDrawFn(url, 7, 7, drawPlayerFallback, PLAYER_YAW),
draw: makeGLBDrawFn(url, gridW, gridH, drawPlayerFallback, PLAYER_YAW),
};
}

Expand Down Expand Up @@ -490,10 +493,16 @@ export const BattleSceneCanvas = forwardRef<
ctx.save();
ctx.translate(offsetX + shakeX, shakeY);

const monsterX = w * 0.3;
const monsterW = w * 0.7;
const monsterY = 0;
const monsterH = h;
const monsterViewX = w * 0.3;
const monsterViewW = w * 0.7;
const monsterViewH = h;

// Scale monster within viewport based on displayScale
const mScale = template?.displayScale ?? 1;
const monsterW = monsterViewW * mScale;
const monsterH = monsterViewH * mScale;
const monsterX = monsterViewX + (monsterViewW - monsterW) / 2; // center
const monsterY = monsterViewH - monsterH; // bottom-align (ground plane)

if (template) {
renderMonster(ctx, template, monsterX, monsterY, monsterW, monsterH, {
Expand Down Expand Up @@ -559,10 +568,21 @@ export const BattleSceneCanvas = forwardRef<

const playerTpl = playerTemplate;
if (playerTpl) {
const playerX = 0;
const playerW = w * 0.35;
const playerY = 0;
const playerH = h;
const playerViewW = w * 0.35;
const playerViewH = h;

// Race-based player scale: Dwarf < Elf < Human
const raceScale = p.userRace === Race.Dwarf ? 0.65
: p.userRace === Race.Elf ? 0.78
: 0.85; // Human / default
// Subtle growth vs stronger monsters (player feels more powerful at higher levels)
const levelBoost = Math.min((template?.level ?? 1) * 0.012, 0.15);
const pScale = Math.min(raceScale + levelBoost, 1.0);

const playerW = playerViewW * pScale;
const playerH = playerViewH * pScale;
const playerX = playerViewW - playerW; // right-align (facing monster)
const playerY = playerViewH - playerH; // bottom-align (ground plane)

// Player hit flash
let playerFlash = 0;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ export type MonsterTemplate = {
/** Minimum luminance for character selection. Default 0.30. Dark creatures: 0.10-0.20 */
charDensityFloor?: number;
};
/** Visual size relative to viewport (0-1). 1 = fills viewport, 0.3 = tiny. */
displayScale?: number;
/** If true, skip template cache — draw function is animated (e.g. GLB creature) */
dynamic?: boolean;
/** Draw silhouette on a pre-filled black canvas at (w x h) pixels */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2331,27 +2331,28 @@ function drawBasiliskRedux(ctx: CanvasRenderingContext2D, w: number, h: number)

export const MONSTER_TEMPLATES_REDUX: MonsterTemplate[] = [
// ── Zone 1: Dark Cave (Levels 1-12) ──
{ id: 'redux-dire-rat', name: 'Dire Rat', gridWidth: 10, gridHeight: 7, dynamic: true, monsterClass: 1, level: 1, atmosphere: { r: 140, g: 110, b: 70, intensity: 0.16 },
draw: makeGLBDrawFn('/models/creatures/dire-rat.glb', 10, 7, drawDireRatRedux) },
{ id: 'redux-kobold', name: 'Kobold', gridWidth: 7, gridHeight: 7, dynamic: true, monsterClass: 2, level: 2, atmosphere: { r: 160, g: 120, b: 50, intensity: 0.16 },
draw: makeGLBDrawFn('/models/creatures/kobold.glb', 7, 7, drawKoboldRedux) },
{ id: 'redux-goblin', name: 'Goblin', gridWidth: 7, gridHeight: 7, dynamic: true, monsterClass: 0, level: 3, atmosphere: { r: 96, g: 120, b: 48, intensity: 0.16 },
draw: makeGLBDrawFn('/models/creatures/goblin.glb', 7, 7, drawGoblinRedux) },
{ id: 'redux-giant-spider', name: 'Giant Spider', gridWidth: 10, gridHeight: 12, dynamic: true, monsterClass: 2, level: 4, atmosphere: { r: 72, g: 164, b: 226, intensity: 0.22 },
// displayScale controls visual size relative to viewport (0-1). Progressive: tiny rat → massive basilisk.
{ id: 'redux-dire-rat', name: 'Dire Rat', gridWidth: 6, gridHeight: 4, displayScale: 0.35, dynamic: true, monsterClass: 1, level: 1, atmosphere: { r: 140, g: 110, b: 70, intensity: 0.16 },
draw: makeGLBDrawFn('/models/creatures/dire-rat.glb', 6, 4, drawDireRatRedux) },
{ id: 'redux-kobold', name: 'Kobold', gridWidth: 7, gridHeight: 6, displayScale: 0.45, dynamic: true, monsterClass: 2, level: 2, atmosphere: { r: 160, g: 120, b: 50, intensity: 0.16 },
draw: makeGLBDrawFn('/models/creatures/kobold.glb', 7, 6, drawKoboldRedux) },
{ id: 'redux-goblin', name: 'Goblin', gridWidth: 8, gridHeight: 7, displayScale: 0.50, dynamic: true, monsterClass: 0, level: 3, atmosphere: { r: 96, g: 120, b: 48, intensity: 0.16 },
draw: makeGLBDrawFn('/models/creatures/goblin.glb', 8, 7, drawGoblinRedux) },
{ id: 'redux-giant-spider', name: 'Giant Spider', gridWidth: 10, gridHeight: 10, displayScale: 0.60, dynamic: true, monsterClass: 2, level: 4, atmosphere: { r: 72, g: 164, b: 226, intensity: 0.22 },
renderOverrides: { gamma: 0.52, ambient: 0.70, brightnessBoost: 2.2, charDensityFloor: 0.10 },
draw: makeGLBDrawFn('/models/creatures/giant-spider.glb', 10, 12, drawPhaseSpiderRedux) },
{ id: 'redux-skeleton', name: 'Skeleton', gridWidth: 7, gridHeight: 7, dynamic: true, monsterClass: 0, level: 5, atmosphere: { r: 96, g: 156, b: 70, intensity: 0.18 },
draw: makeGLBDrawFn('/models/creatures/skeleton.glb', 7, 7, drawSkeletonRedux) },
{ id: 'redux-goblin-shaman', name: 'Goblin Shaman', gridWidth: 7, gridHeight: 7, dynamic: true, monsterClass: 1, level: 6, atmosphere: { r: 106, g: 70, b: 164, intensity: 0.18 },
draw: makeGLBDrawFn('/models/creatures/goblin-shaman.glb', 7, 7, drawGoblinShamanRedux) },
{ id: 'redux-gelatinous-ooze', name: 'Gelatinous Ooze', gridWidth: 10, gridHeight: 14, monsterClass: 2, level: 7, atmosphere: { r: 66, g: 182, b: 56, intensity: 0.18 }, draw: drawGelatinousOozeRedux },
{ id: 'redux-bugbear', name: 'Bugbear', gridWidth: 7, gridHeight: 7, dynamic: true, monsterClass: 0, level: 8, atmosphere: { r: 172, g: 138, b: 72, intensity: 0.16 },
draw: makeGLBDrawFn('/models/creatures/bugbear.glb', 7, 7, drawBugbearRedux) },
{ id: 'redux-carrion-crawler', name: 'Carrion Crawler', gridWidth: 10, gridHeight: 13, dynamic: true, monsterClass: 1, level: 9, atmosphere: { r: 144, g: 168, b: 208, intensity: 0.20 },
draw: makeGLBDrawFn('/models/creatures/giant-spider.glb', 10, 10, drawPhaseSpiderRedux) },
{ id: 'redux-skeleton', name: 'Skeleton', gridWidth: 7, gridHeight: 10, displayScale: 0.55, dynamic: true, monsterClass: 0, level: 5, atmosphere: { r: 96, g: 156, b: 70, intensity: 0.18 },
draw: makeGLBDrawFn('/models/creatures/skeleton.glb', 7, 10, drawSkeletonRedux) },
{ id: 'redux-goblin-shaman', name: 'Goblin Shaman', gridWidth: 8, gridHeight: 9, displayScale: 0.58, dynamic: true, monsterClass: 1, level: 6, atmosphere: { r: 106, g: 70, b: 164, intensity: 0.18 },
draw: makeGLBDrawFn('/models/creatures/goblin-shaman.glb', 8, 9, drawGoblinShamanRedux) },
{ id: 'redux-gelatinous-ooze', name: 'Gelatinous Ooze', gridWidth: 11, gridHeight: 14, displayScale: 0.70, monsterClass: 2, level: 7, atmosphere: { r: 66, g: 182, b: 56, intensity: 0.18 }, draw: drawGelatinousOozeRedux },
{ id: 'redux-bugbear', name: 'Bugbear', gridWidth: 10, gridHeight: 12, displayScale: 0.75, dynamic: true, monsterClass: 0, level: 8, atmosphere: { r: 172, g: 138, b: 72, intensity: 0.16 },
draw: makeGLBDrawFn('/models/creatures/bugbear.glb', 10, 12, drawBugbearRedux) },
{ id: 'redux-carrion-crawler', name: 'Carrion Crawler', gridWidth: 12, gridHeight: 13, displayScale: 0.80, dynamic: true, monsterClass: 1, level: 9, atmosphere: { r: 144, g: 168, b: 208, intensity: 0.20 },
renderOverrides: { gamma: 0.52, ambient: 0.70, brightnessBoost: 2.0, charDensityFloor: 0.10 },
draw: drawCarrionCrawlerRedux },
{ id: 'redux-hook-horror', name: 'Hook Horror', gridWidth: 14, gridHeight: 12, dynamic: true, monsterClass: 2, level: 10, atmosphere: { r: 136, g: 82, b: 178, intensity: 0.18 },
draw: makeGLBDrawFn('/models/creatures/hook-horror.glb', 14, 12, drawDuskDrakeRedux) },
{ id: 'redux-basilisk', name: 'Basilisk', gridWidth: 26, gridHeight: 9, dynamic: true, monsterClass: 0, level: 12, isBoss: true, atmosphere: { r: 52, g: 86, b: 36, intensity: 0.16 }, renderOverrides: { gamma: 0.52, ambient: 0.60, brightnessBoost: 1.7, charDensityFloor: 0.10 },
draw: makeGLBDrawFn('/models/creatures/basilisk.glb', 26, 9, drawBasiliskRedux) },
{ id: 'redux-hook-horror', name: 'Hook Horror', gridWidth: 14, gridHeight: 14, displayScale: 0.85, dynamic: true, monsterClass: 2, level: 10, atmosphere: { r: 136, g: 82, b: 178, intensity: 0.18 },
draw: makeGLBDrawFn('/models/creatures/hook-horror.glb', 14, 14, drawDuskDrakeRedux) },
{ id: 'redux-basilisk', name: 'Basilisk', gridWidth: 18, gridHeight: 18, displayScale: 1.0, dynamic: true, monsterClass: 0, level: 12, isBoss: true, atmosphere: { r: 52, g: 86, b: 36, intensity: 0.16 }, renderOverrides: { gamma: 0.52, ambient: 0.60, brightnessBoost: 1.7, charDensityFloor: 0.10 },
draw: makeGLBDrawFn('/models/creatures/basilisk.glb', 18, 18, drawBasiliskRedux) },
];
Loading