Expand campaign visual assets
BIN
src/assets/images/portraits/huang-quan.png
Normal file
|
After Width: | Height: | Size: 2.2 MiB |
BIN
src/assets/images/portraits/ma-liang.png
Normal file
|
After Width: | Height: | Size: 2.2 MiB |
BIN
src/assets/images/portraits/sun-qian.png
Normal file
|
After Width: | Height: | Size: 2.2 MiB |
BIN
src/assets/images/portraits/wang-ping.png
Normal file
|
After Width: | Height: | Size: 2.2 MiB |
BIN
src/assets/images/story/33-yellow-pursuit-refugees.png
Normal file
|
After Width: | Height: | Size: 2.4 MiB |
BIN
src/assets/images/story/34-xuzhou-gates-handover.png
Normal file
|
After Width: | Height: | Size: 2.2 MiB |
BIN
src/assets/images/story/35-wolong-night-strategy.png
Normal file
|
After Width: | Height: | Size: 2.1 MiB |
BIN
src/assets/images/story/36-red-cliffs-chain-fleet.png
Normal file
|
After Width: | Height: | Size: 2.1 MiB |
BIN
src/assets/images/units/unit-rebel-ragged-actions.png
Normal file
|
After Width: | Height: | Size: 1.1 MiB |
BIN
src/assets/images/units/unit-rebel-ragged.png
Normal file
|
After Width: | Height: | Size: 1.2 MiB |
BIN
src/assets/images/units/unit-wei-infantry-veteran-actions.png
Normal file
|
After Width: | Height: | Size: 1.1 MiB |
BIN
src/assets/images/units/unit-wei-infantry-veteran.png
Normal file
|
After Width: | Height: | Size: 1.1 MiB |
69
src/game/data/portraitAssets.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
const portraitModules = import.meta.glob('../../assets/images/portraits/*.png', {
|
||||
eager: true,
|
||||
import: 'default'
|
||||
}) as Record<string, string>;
|
||||
|
||||
const legacyPortraitSlugs: Record<string, string> = {
|
||||
liuBei: 'liu-bei',
|
||||
guanYu: 'guan-yu',
|
||||
zhangFei: 'zhang-fei',
|
||||
zhugeLiang: 'zhuge-liang',
|
||||
zhaoYun: 'zhao-yun',
|
||||
jiangWei: 'jiang-wei',
|
||||
simaYi: 'sima-yi'
|
||||
};
|
||||
|
||||
const speakerPortraitKeys: Record<string, string> = {
|
||||
유비: 'liuBei',
|
||||
관우: 'guanYu',
|
||||
장비: 'zhangFei',
|
||||
제갈량: 'zhugeLiang',
|
||||
조운: 'zhaoYun',
|
||||
강유: 'jiangWei',
|
||||
사마의: 'simaYi',
|
||||
미축: 'mi-zhu',
|
||||
간옹: 'jian-yong',
|
||||
손건: 'sun-qian',
|
||||
마량: 'ma-liang',
|
||||
방통: 'pang-tong',
|
||||
법정: 'fa-zheng',
|
||||
황충: 'huang-zhong',
|
||||
위연: 'wei-yan',
|
||||
마초: 'ma-chao',
|
||||
마대: 'ma-dai',
|
||||
왕평: 'wang-ping',
|
||||
이엄: 'li-yan',
|
||||
오의: 'wu-yi',
|
||||
이적: 'yi-ji',
|
||||
공지: 'gong-zhi',
|
||||
황권: 'huang-quan',
|
||||
맹획: 'meng-huo',
|
||||
주유: 'zhou-yu',
|
||||
조조: 'cao-cao'
|
||||
};
|
||||
|
||||
function fileSlugFromPath(path: string) {
|
||||
const fileName = path.split('/').pop() ?? path;
|
||||
return fileName.replace(/\.png$/i, '');
|
||||
}
|
||||
|
||||
function camelToKebab(value: string) {
|
||||
return value.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`);
|
||||
}
|
||||
|
||||
export const portraitAssets: Record<string, string> = Object.fromEntries(
|
||||
Object.entries(portraitModules).map(([path, url]) => [fileSlugFromPath(path), url])
|
||||
);
|
||||
|
||||
export function portraitSlugForKey(key: string) {
|
||||
return legacyPortraitSlugs[key] ?? camelToKebab(key);
|
||||
}
|
||||
|
||||
export function portraitTextureKey(key: string) {
|
||||
const slug = portraitSlugForKey(key);
|
||||
return portraitAssets[slug] ? `portrait-${slug}` : undefined;
|
||||
}
|
||||
|
||||
export function portraitKeyForSpeaker(speaker?: string) {
|
||||
return speaker ? speakerPortraitKeys[speaker] : undefined;
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { TerrainType, UnitClassKey } from './battleRules';
|
||||
import type { EquipmentSet } from './battleItems';
|
||||
|
||||
export type PortraitKey = 'liuBei' | 'guanYu' | 'zhangFei' | 'zhugeLiang' | 'zhaoYun' | 'jiangWei' | 'simaYi';
|
||||
export type PortraitKey = string;
|
||||
|
||||
export type StoryPage = {
|
||||
id: string;
|
||||
|
||||
@@ -1,68 +1,54 @@
|
||||
import storyChaosUrl from '../../assets/images/story/01-yellow-turban-chaos.png';
|
||||
import storyLiuBeiUrl from '../../assets/images/story/02-liu-bei-resolve.png';
|
||||
import storyThreeHeroesUrl from '../../assets/images/story/03-three-heroes-meet.png';
|
||||
import storyMilitiaUrl from '../../assets/images/story/04-volunteer-militia.png';
|
||||
import storySortieUrl from '../../assets/images/story/05-first-sortie.png';
|
||||
import storyYellowPursuitUrl from '../../assets/images/story/06-yellow-turban-pursuit.png';
|
||||
import storyAntiDongPassUrl from '../../assets/images/story/07-anti-dong-pass.png';
|
||||
import storyXuzhouHandoverUrl from '../../assets/images/story/08-xuzhou-handover.png';
|
||||
import storyWanderingRoadUrl from '../../assets/images/story/09-wandering-road.png';
|
||||
import storyWolongCottageUrl from '../../assets/images/story/10-wolong-cottage.png';
|
||||
import storyRedCliffsFireUrl from '../../assets/images/story/11-red-cliffs-fire.png';
|
||||
import storyYizhouMountainPassUrl from '../../assets/images/story/12-yizhou-mountain-pass.png';
|
||||
import storyNorthernExpeditionUrl from '../../assets/images/story/13-northern-expedition.png';
|
||||
import storyNanzhongPacificationUrl from '../../assets/images/story/14-nanzhong-pacification.png';
|
||||
import storyYilingBaidiAftermathUrl from '../../assets/images/story/15-yiling-baidi-aftermath.png';
|
||||
import storyQishanRenewedOffensiveUrl from '../../assets/images/story/16-qishan-renewed-offensive.png';
|
||||
import storyTianshuiFrontUrl from '../../assets/images/story/17-tianshui-front.png';
|
||||
import storyJietingCrisisUrl from '../../assets/images/story/18-jieting-crisis.png';
|
||||
import storyChencangSiegeUrl from '../../assets/images/story/19-chencang-siege.png';
|
||||
import storyWuduYinpingUrl from '../../assets/images/story/20-wudu-yinping.png';
|
||||
import storyHanzhongRainDefenseUrl from '../../assets/images/story/21-hanzhong-rain-defense.png';
|
||||
import storyLuchengPursuitUrl from '../../assets/images/story/22-lucheng-pursuit.png';
|
||||
import storyWeishuiCampsUrl from '../../assets/images/story/23-weishui-camps.png';
|
||||
import storyWeishuiNorthbankUrl from '../../assets/images/story/24-weishui-northbank.png';
|
||||
import storyYizhouLuoRoadUrl from '../../assets/images/story/25-yizhou-luo-road.png';
|
||||
import storyChengduSurrenderUrl from '../../assets/images/story/26-chengdu-surrender.png';
|
||||
import storyJingzhouCrisisUrl from '../../assets/images/story/27-jingzhou-crisis.png';
|
||||
import storyNanzhongCapturesUrl from '../../assets/images/story/28-nanzhong-seven-captures.png';
|
||||
import storyFanCastleFloodUrl from '../../assets/images/story/29-fan-castle-flood.png';
|
||||
import storyMaichengIsolationUrl from '../../assets/images/story/30-maicheng-isolation.png';
|
||||
import storyYilingFireAttackUrl from '../../assets/images/story/31-yiling-fire-attack.png';
|
||||
import storyBaidiEntrustmentUrl from '../../assets/images/story/32-baidi-entrustment.png';
|
||||
const storyModules = import.meta.glob('../../assets/images/story/*.png', {
|
||||
eager: true,
|
||||
import: 'default'
|
||||
}) as Record<string, string>;
|
||||
|
||||
export const storyBackgroundAssets: Record<string, string> = {
|
||||
'story-chaos': storyChaosUrl,
|
||||
'story-liu-bei': storyLiuBeiUrl,
|
||||
'story-three-heroes': storyThreeHeroesUrl,
|
||||
'story-militia': storyMilitiaUrl,
|
||||
'story-sortie': storySortieUrl,
|
||||
'story-yellow-pursuit': storyYellowPursuitUrl,
|
||||
'story-anti-dong': storyAntiDongPassUrl,
|
||||
'story-xuzhou': storyXuzhouHandoverUrl,
|
||||
'story-wandering': storyWanderingRoadUrl,
|
||||
'story-wolong': storyWolongCottageUrl,
|
||||
'story-red-cliffs': storyRedCliffsFireUrl,
|
||||
'story-yizhou': storyYizhouMountainPassUrl,
|
||||
'story-northern': storyNorthernExpeditionUrl,
|
||||
'story-nanzhong': storyNanzhongPacificationUrl,
|
||||
'story-yiling-baidi': storyYilingBaidiAftermathUrl,
|
||||
'story-qishan-renewed': storyQishanRenewedOffensiveUrl,
|
||||
'story-tianshui-front': storyTianshuiFrontUrl,
|
||||
'story-jieting-crisis': storyJietingCrisisUrl,
|
||||
'story-chencang-siege': storyChencangSiegeUrl,
|
||||
'story-wudu-yinping': storyWuduYinpingUrl,
|
||||
'story-hanzhong-rain': storyHanzhongRainDefenseUrl,
|
||||
'story-lucheng-pursuit': storyLuchengPursuitUrl,
|
||||
'story-weishui-camps': storyWeishuiCampsUrl,
|
||||
'story-weishui-northbank': storyWeishuiNorthbankUrl,
|
||||
'story-yizhou-luo-road': storyYizhouLuoRoadUrl,
|
||||
'story-chengdu-surrender': storyChengduSurrenderUrl,
|
||||
'story-jingzhou-crisis': storyJingzhouCrisisUrl,
|
||||
'story-nanzhong-captures': storyNanzhongCapturesUrl,
|
||||
'story-fan-castle-flood': storyFanCastleFloodUrl,
|
||||
'story-maicheng-isolation': storyMaichengIsolationUrl,
|
||||
'story-yiling-fire-attack': storyYilingFireAttackUrl,
|
||||
'story-baidi-entrustment': storyBaidiEntrustmentUrl,
|
||||
'story-resolve': storyLiuBeiUrl
|
||||
const storyAssetAliases: Record<string, string> = {
|
||||
'story-chaos': 'story-yellow-turban-chaos',
|
||||
'story-liu-bei': 'story-liu-bei-resolve',
|
||||
'story-three-heroes': 'story-three-heroes-meet',
|
||||
'story-militia': 'story-volunteer-militia',
|
||||
'story-sortie': 'story-first-sortie',
|
||||
'story-yellow-pursuit': 'story-yellow-turban-pursuit',
|
||||
'story-anti-dong': 'story-anti-dong-pass',
|
||||
'story-xuzhou': 'story-xuzhou-handover',
|
||||
'story-wandering': 'story-wandering-road',
|
||||
'story-wolong': 'story-wolong-cottage',
|
||||
'story-red-cliffs': 'story-red-cliffs-fire',
|
||||
'story-yizhou': 'story-yizhou-mountain-pass',
|
||||
'story-northern': 'story-northern-expedition',
|
||||
'story-nanzhong': 'story-nanzhong-pacification',
|
||||
'story-yiling-baidi': 'story-yiling-baidi-aftermath',
|
||||
'story-qishan-renewed': 'story-qishan-renewed-offensive',
|
||||
'story-hanzhong-rain': 'story-hanzhong-rain-defense',
|
||||
'story-nanzhong-captures': 'story-nanzhong-seven-captures',
|
||||
'story-resolve': 'story-liu-bei-resolve'
|
||||
};
|
||||
|
||||
function storyKeyFromPath(path: string) {
|
||||
const fileName = path.split('/').pop() ?? path;
|
||||
const slug = fileName.replace(/\.png$/i, '').replace(/^\d+-/, '');
|
||||
return `story-${slug}`;
|
||||
}
|
||||
|
||||
const discoveredStoryAssets = Object.fromEntries(
|
||||
Object.entries(storyModules).map(([path, url]) => [storyKeyFromPath(path), url])
|
||||
);
|
||||
|
||||
export const storyBackgroundAssets: Record<string, string> = { ...discoveredStoryAssets };
|
||||
|
||||
Object.entries(storyAssetAliases).forEach(([alias, target]) => {
|
||||
const targetUrl = storyBackgroundAssets[target];
|
||||
if (targetUrl) {
|
||||
storyBackgroundAssets[alias] = targetUrl;
|
||||
}
|
||||
});
|
||||
|
||||
export function storyBackgroundKeysFor(baseKey: string) {
|
||||
const prefix = `${baseKey}-`;
|
||||
const keys = Object.keys(storyBackgroundAssets)
|
||||
.filter((key) => key === baseKey || key.startsWith(prefix))
|
||||
.sort();
|
||||
|
||||
return Array.from(new Set(keys));
|
||||
}
|
||||
|
||||
@@ -8266,7 +8266,7 @@ export class BattleScene extends Phaser.Scene {
|
||||
unit.id.startsWith('northern-second-cavalry') ||
|
||||
unit.id.startsWith('northern-third-cavalry')
|
||||
) {
|
||||
return 'unit-rebel-cavalry';
|
||||
return this.resolveUnitTextureVariant('unit-rebel-cavalry', unit.id);
|
||||
}
|
||||
if (
|
||||
unit.id.startsWith('northern-first-archer') ||
|
||||
@@ -8276,7 +8276,7 @@ export class BattleScene extends Phaser.Scene {
|
||||
unit.id.startsWith('northern-second-strategist') ||
|
||||
unit.id.startsWith('northern-third-strategist')
|
||||
) {
|
||||
return 'unit-rebel-archer';
|
||||
return this.resolveUnitTextureVariant('unit-rebel-archer', unit.id);
|
||||
}
|
||||
if (
|
||||
unit.id.startsWith('northern-first-officer') ||
|
||||
@@ -8286,9 +8286,9 @@ export class BattleScene extends Phaser.Scene {
|
||||
unit.id === 'northern-second-leader-guo-huai' ||
|
||||
unit.id === 'northern-third-leader-zhang-he'
|
||||
) {
|
||||
return 'unit-rebel-leader';
|
||||
return this.resolveUnitTextureVariant('unit-rebel-leader', unit.id);
|
||||
}
|
||||
return unitTexture[unit.id] ?? unitTextureByClass[unit.classKey] ?? 'unit-rebel';
|
||||
return this.resolveUnitTextureVariant(unitTexture[unit.id] ?? unitTextureByClass[unit.classKey] ?? 'unit-rebel', unit.id);
|
||||
}
|
||||
|
||||
private signatureUnitTextureKey(unit: UnitData) {
|
||||
@@ -8306,65 +8306,90 @@ export class BattleScene extends Phaser.Scene {
|
||||
if (coreAllyTextureIds.has(unit.id)) {
|
||||
return undefined;
|
||||
}
|
||||
return this.unitTextureForClass('shu', unit.classKey);
|
||||
return this.unitTextureForClass('shu', unit.classKey, unit.id);
|
||||
}
|
||||
|
||||
if (earlyRebelTexturePrefixes.some((prefix) => unit.id.startsWith(prefix))) {
|
||||
return undefined;
|
||||
}
|
||||
if (nanmanTexturePrefixes.some((prefix) => unit.id.startsWith(prefix))) {
|
||||
return this.unitTextureForClass('nanman', unit.classKey);
|
||||
return this.unitTextureForClass('nanman', unit.classKey, unit.id);
|
||||
}
|
||||
if (wuTexturePrefixes.some((prefix) => unit.id.startsWith(prefix))) {
|
||||
return this.unitTextureForClass('wu', unit.classKey);
|
||||
return this.unitTextureForClass('wu', unit.classKey, unit.id);
|
||||
}
|
||||
|
||||
return this.unitTextureForClass('wei', unit.classKey);
|
||||
return this.unitTextureForClass('wei', unit.classKey, unit.id);
|
||||
}
|
||||
|
||||
private unitTextureForClass(style: 'shu' | 'wei' | 'wu' | 'nanman', classKey: UnitClassKey) {
|
||||
private unitTextureForClass(style: 'shu' | 'wei' | 'wu' | 'nanman', classKey: UnitClassKey, seed: string) {
|
||||
let baseKey: string;
|
||||
|
||||
if (style === 'shu') {
|
||||
if (classKey === 'spearman') {
|
||||
return 'unit-shu-spearman';
|
||||
baseKey = 'unit-shu-spearman';
|
||||
} else if (classKey === 'archer') {
|
||||
baseKey = 'unit-shu-archer';
|
||||
} else if (classKey === 'cavalry') {
|
||||
baseKey = 'unit-shu-cavalry';
|
||||
} else if (classKey === 'strategist' || classKey === 'quartermaster') {
|
||||
baseKey = 'unit-shu-strategist';
|
||||
} else if (classKey === 'rebelLeader') {
|
||||
baseKey = 'unit-shu-officer';
|
||||
} else {
|
||||
baseKey = 'unit-shu-infantry';
|
||||
}
|
||||
if (classKey === 'archer') {
|
||||
return 'unit-shu-archer';
|
||||
}
|
||||
if (classKey === 'cavalry') {
|
||||
return 'unit-shu-cavalry';
|
||||
}
|
||||
if (classKey === 'strategist' || classKey === 'quartermaster') {
|
||||
return 'unit-shu-strategist';
|
||||
}
|
||||
if (classKey === 'rebelLeader') {
|
||||
return 'unit-shu-officer';
|
||||
}
|
||||
return 'unit-shu-infantry';
|
||||
}
|
||||
|
||||
if (style === 'nanman') {
|
||||
} else if (style === 'nanman') {
|
||||
if (classKey === 'archer' || classKey === 'strategist' || classKey === 'quartermaster') {
|
||||
return 'unit-nanman-shaman';
|
||||
baseKey = 'unit-nanman-shaman';
|
||||
} else if (classKey === 'rebelLeader' || classKey === 'cavalry') {
|
||||
baseKey = 'unit-nanman-officer';
|
||||
} else {
|
||||
baseKey = 'unit-nanman-infantry';
|
||||
}
|
||||
if (classKey === 'rebelLeader' || classKey === 'cavalry') {
|
||||
return 'unit-nanman-officer';
|
||||
}
|
||||
return 'unit-nanman-infantry';
|
||||
} else if (classKey === 'archer') {
|
||||
baseKey = `unit-${style}-archer`;
|
||||
} else if (classKey === 'cavalry') {
|
||||
baseKey = `unit-${style}-cavalry`;
|
||||
} else if (classKey === 'strategist' || classKey === 'quartermaster') {
|
||||
baseKey = `unit-${style}-strategist`;
|
||||
} else if (classKey === 'rebelLeader') {
|
||||
baseKey = `unit-${style}-officer`;
|
||||
} else {
|
||||
baseKey = `unit-${style}-infantry`;
|
||||
}
|
||||
|
||||
if (classKey === 'archer') {
|
||||
return `unit-${style}-archer`;
|
||||
return this.resolveUnitTextureVariant(baseKey, seed);
|
||||
}
|
||||
|
||||
private resolveUnitTextureVariant(baseKey: string, seed: string) {
|
||||
const variantKeys = this.unitTextureVariantKeys(baseKey);
|
||||
if (variantKeys.length <= 1) {
|
||||
return baseKey;
|
||||
}
|
||||
if (classKey === 'cavalry') {
|
||||
return `unit-${style}-cavalry`;
|
||||
|
||||
return variantKeys[this.hashString(seed) % variantKeys.length];
|
||||
}
|
||||
|
||||
private unitTextureVariantKeys(baseKey: string) {
|
||||
const prefix = `${baseKey}-`;
|
||||
return this.textures
|
||||
.getTextureKeys()
|
||||
.filter((key) => {
|
||||
if (key === baseKey) {
|
||||
return true;
|
||||
}
|
||||
return key.startsWith(prefix) && !key.endsWith('-actions') && this.textures.exists(`${key}-actions`);
|
||||
})
|
||||
.sort();
|
||||
}
|
||||
|
||||
private hashString(value: string) {
|
||||
let hash = 0;
|
||||
for (let index = 0; index < value.length; index += 1) {
|
||||
hash = (hash * 31 + value.charCodeAt(index)) >>> 0;
|
||||
}
|
||||
if (classKey === 'strategist' || classKey === 'quartermaster') {
|
||||
return `unit-${style}-strategist`;
|
||||
}
|
||||
if (classKey === 'rebelLeader') {
|
||||
return `unit-${style}-officer`;
|
||||
}
|
||||
return `unit-${style}-infantry`;
|
||||
return hash;
|
||||
}
|
||||
|
||||
private unitActionTexture(unit: UnitData) {
|
||||
|
||||
@@ -1,108 +1,6 @@
|
||||
import Phaser from 'phaser';
|
||||
import guanYuPortraitUrl from '../../assets/images/portraits/guan-yu.png';
|
||||
import jiangWeiPortraitUrl from '../../assets/images/portraits/jiang-wei.png';
|
||||
import liuBeiPortraitUrl from '../../assets/images/portraits/liu-bei.png';
|
||||
import simaYiPortraitUrl from '../../assets/images/portraits/sima-yi.png';
|
||||
import zhaoYunPortraitUrl from '../../assets/images/portraits/zhao-yun.png';
|
||||
import zhangFeiPortraitUrl from '../../assets/images/portraits/zhang-fei.png';
|
||||
import zhugeLiangPortraitUrl from '../../assets/images/portraits/zhuge-liang.png';
|
||||
import titleBackgroundUrl from '../../assets/images/taoyuan-oath-title.png';
|
||||
import caoCaoActionSheetUrl from '../../assets/images/units/unit-cao-cao-actions.png';
|
||||
import caoCaoUnitSheetUrl from '../../assets/images/units/unit-cao-cao.png';
|
||||
import faZhengActionSheetUrl from '../../assets/images/units/unit-fa-zheng-actions.png';
|
||||
import faZhengUnitSheetUrl from '../../assets/images/units/unit-fa-zheng.png';
|
||||
import gongZhiActionSheetUrl from '../../assets/images/units/unit-gong-zhi-actions.png';
|
||||
import gongZhiUnitSheetUrl from '../../assets/images/units/unit-gong-zhi.png';
|
||||
import guanYuActionSheetUrl from '../../assets/images/units/unit-guan-yu-actions.png';
|
||||
import guanYuUnitSheetUrl from '../../assets/images/units/unit-guan-yu.png';
|
||||
import huangQuanActionSheetUrl from '../../assets/images/units/unit-huang-quan-actions.png';
|
||||
import huangQuanUnitSheetUrl from '../../assets/images/units/unit-huang-quan.png';
|
||||
import huangZhongActionSheetUrl from '../../assets/images/units/unit-huang-zhong-actions.png';
|
||||
import huangZhongUnitSheetUrl from '../../assets/images/units/unit-huang-zhong.png';
|
||||
import jiangWeiActionSheetUrl from '../../assets/images/units/unit-jiang-wei-actions.png';
|
||||
import jiangWeiUnitSheetUrl from '../../assets/images/units/unit-jiang-wei.png';
|
||||
import liYanActionSheetUrl from '../../assets/images/units/unit-li-yan-actions.png';
|
||||
import liYanUnitSheetUrl from '../../assets/images/units/unit-li-yan.png';
|
||||
import liuBeiActionSheetUrl from '../../assets/images/units/unit-liu-bei-actions.png';
|
||||
import liuBeiUnitSheetUrl from '../../assets/images/units/unit-liu-bei.png';
|
||||
import luBuActionSheetUrl from '../../assets/images/units/unit-lu-bu-actions.png';
|
||||
import luBuUnitSheetUrl from '../../assets/images/units/unit-lu-bu.png';
|
||||
import luMengActionSheetUrl from '../../assets/images/units/unit-lu-meng-actions.png';
|
||||
import luMengUnitSheetUrl from '../../assets/images/units/unit-lu-meng.png';
|
||||
import maChaoActionSheetUrl from '../../assets/images/units/unit-ma-chao-actions.png';
|
||||
import maChaoUnitSheetUrl from '../../assets/images/units/unit-ma-chao.png';
|
||||
import maDaiActionSheetUrl from '../../assets/images/units/unit-ma-dai-actions.png';
|
||||
import maDaiUnitSheetUrl from '../../assets/images/units/unit-ma-dai.png';
|
||||
import maLiangActionSheetUrl from '../../assets/images/units/unit-ma-liang-actions.png';
|
||||
import maLiangUnitSheetUrl from '../../assets/images/units/unit-ma-liang.png';
|
||||
import mengHuoActionSheetUrl from '../../assets/images/units/unit-meng-huo-actions.png';
|
||||
import mengHuoUnitSheetUrl from '../../assets/images/units/unit-meng-huo.png';
|
||||
import pangTongActionSheetUrl from '../../assets/images/units/unit-pang-tong-actions.png';
|
||||
import pangTongUnitSheetUrl from '../../assets/images/units/unit-pang-tong.png';
|
||||
import rebelArcherActionSheetUrl from '../../assets/images/units/unit-rebel-archer-actions.png';
|
||||
import rebelArcherUnitSheetUrl from '../../assets/images/units/unit-rebel-archer.png';
|
||||
import rebelCavalryActionSheetUrl from '../../assets/images/units/unit-rebel-cavalry-actions.png';
|
||||
import rebelCavalryUnitSheetUrl from '../../assets/images/units/unit-rebel-cavalry.png';
|
||||
import rebelLeaderActionSheetUrl from '../../assets/images/units/unit-rebel-leader-actions.png';
|
||||
import rebelLeaderUnitSheetUrl from '../../assets/images/units/unit-rebel-leader.png';
|
||||
import rebelActionSheetUrl from '../../assets/images/units/unit-rebel-actions.png';
|
||||
import rebelUnitSheetUrl from '../../assets/images/units/unit-rebel.png';
|
||||
import nanmanInfantryActionSheetUrl from '../../assets/images/units/unit-nanman-infantry-actions.png';
|
||||
import nanmanInfantryUnitSheetUrl from '../../assets/images/units/unit-nanman-infantry.png';
|
||||
import nanmanOfficerActionSheetUrl from '../../assets/images/units/unit-nanman-officer-actions.png';
|
||||
import nanmanOfficerUnitSheetUrl from '../../assets/images/units/unit-nanman-officer.png';
|
||||
import nanmanShamanActionSheetUrl from '../../assets/images/units/unit-nanman-shaman-actions.png';
|
||||
import nanmanShamanUnitSheetUrl from '../../assets/images/units/unit-nanman-shaman.png';
|
||||
import shuArcherActionSheetUrl from '../../assets/images/units/unit-shu-archer-actions.png';
|
||||
import shuArcherUnitSheetUrl from '../../assets/images/units/unit-shu-archer.png';
|
||||
import shuCavalryActionSheetUrl from '../../assets/images/units/unit-shu-cavalry-actions.png';
|
||||
import shuCavalryUnitSheetUrl from '../../assets/images/units/unit-shu-cavalry.png';
|
||||
import shuInfantryActionSheetUrl from '../../assets/images/units/unit-shu-infantry-actions.png';
|
||||
import shuInfantryUnitSheetUrl from '../../assets/images/units/unit-shu-infantry.png';
|
||||
import shuOfficerActionSheetUrl from '../../assets/images/units/unit-shu-officer-actions.png';
|
||||
import shuOfficerUnitSheetUrl from '../../assets/images/units/unit-shu-officer.png';
|
||||
import shuSpearmanActionSheetUrl from '../../assets/images/units/unit-shu-spearman-actions.png';
|
||||
import shuSpearmanUnitSheetUrl from '../../assets/images/units/unit-shu-spearman.png';
|
||||
import shuStrategistActionSheetUrl from '../../assets/images/units/unit-shu-strategist-actions.png';
|
||||
import shuStrategistUnitSheetUrl from '../../assets/images/units/unit-shu-strategist.png';
|
||||
import simaYiActionSheetUrl from '../../assets/images/units/unit-sima-yi-actions.png';
|
||||
import simaYiUnitSheetUrl from '../../assets/images/units/unit-sima-yi.png';
|
||||
import wangPingActionSheetUrl from '../../assets/images/units/unit-wang-ping-actions.png';
|
||||
import wangPingUnitSheetUrl from '../../assets/images/units/unit-wang-ping.png';
|
||||
import weiArcherActionSheetUrl from '../../assets/images/units/unit-wei-archer-actions.png';
|
||||
import weiArcherUnitSheetUrl from '../../assets/images/units/unit-wei-archer.png';
|
||||
import weiCavalryActionSheetUrl from '../../assets/images/units/unit-wei-cavalry-actions.png';
|
||||
import weiCavalryUnitSheetUrl from '../../assets/images/units/unit-wei-cavalry.png';
|
||||
import weiInfantryActionSheetUrl from '../../assets/images/units/unit-wei-infantry-actions.png';
|
||||
import weiInfantryUnitSheetUrl from '../../assets/images/units/unit-wei-infantry.png';
|
||||
import weiOfficerActionSheetUrl from '../../assets/images/units/unit-wei-officer-actions.png';
|
||||
import weiOfficerUnitSheetUrl from '../../assets/images/units/unit-wei-officer.png';
|
||||
import weiStrategistActionSheetUrl from '../../assets/images/units/unit-wei-strategist-actions.png';
|
||||
import weiStrategistUnitSheetUrl from '../../assets/images/units/unit-wei-strategist.png';
|
||||
import weiYanActionSheetUrl from '../../assets/images/units/unit-wei-yan-actions.png';
|
||||
import weiYanUnitSheetUrl from '../../assets/images/units/unit-wei-yan.png';
|
||||
import wuArcherActionSheetUrl from '../../assets/images/units/unit-wu-archer-actions.png';
|
||||
import wuArcherUnitSheetUrl from '../../assets/images/units/unit-wu-archer.png';
|
||||
import wuCavalryActionSheetUrl from '../../assets/images/units/unit-wu-cavalry-actions.png';
|
||||
import wuCavalryUnitSheetUrl from '../../assets/images/units/unit-wu-cavalry.png';
|
||||
import wuInfantryActionSheetUrl from '../../assets/images/units/unit-wu-infantry-actions.png';
|
||||
import wuInfantryUnitSheetUrl from '../../assets/images/units/unit-wu-infantry.png';
|
||||
import wuOfficerActionSheetUrl from '../../assets/images/units/unit-wu-officer-actions.png';
|
||||
import wuOfficerUnitSheetUrl from '../../assets/images/units/unit-wu-officer.png';
|
||||
import wuStrategistActionSheetUrl from '../../assets/images/units/unit-wu-strategist-actions.png';
|
||||
import wuStrategistUnitSheetUrl from '../../assets/images/units/unit-wu-strategist.png';
|
||||
import wuYiActionSheetUrl from '../../assets/images/units/unit-wu-yi-actions.png';
|
||||
import wuYiUnitSheetUrl from '../../assets/images/units/unit-wu-yi.png';
|
||||
import yanYanActionSheetUrl from '../../assets/images/units/unit-yan-yan-actions.png';
|
||||
import yanYanUnitSheetUrl from '../../assets/images/units/unit-yan-yan.png';
|
||||
import yiJiActionSheetUrl from '../../assets/images/units/unit-yi-ji-actions.png';
|
||||
import yiJiUnitSheetUrl from '../../assets/images/units/unit-yi-ji.png';
|
||||
import zhangFeiActionSheetUrl from '../../assets/images/units/unit-zhang-fei-actions.png';
|
||||
import zhangFeiUnitSheetUrl from '../../assets/images/units/unit-zhang-fei.png';
|
||||
import zhaoYunActionSheetUrl from '../../assets/images/units/unit-zhao-yun-actions.png';
|
||||
import zhaoYunUnitSheetUrl from '../../assets/images/units/unit-zhao-yun.png';
|
||||
import zhugeLiangActionSheetUrl from '../../assets/images/units/unit-zhuge-liang-actions.png';
|
||||
import zhugeLiangUnitSheetUrl from '../../assets/images/units/unit-zhuge-liang.png';
|
||||
import { portraitAssets } from '../data/portraitAssets';
|
||||
import { storyBackgroundAssets } from '../data/storyAssets';
|
||||
|
||||
type UnitDirection = 'south' | 'east' | 'north' | 'west';
|
||||
@@ -118,56 +16,37 @@ const unitSheetRows: Record<UnitDirection, number> = {
|
||||
west: 3
|
||||
};
|
||||
|
||||
const sourceUnitSheets = [
|
||||
{ key: 'unit-liu-bei', url: liuBeiUnitSheetUrl, actionKey: 'unit-liu-bei-actions', actionUrl: liuBeiActionSheetUrl },
|
||||
{ key: 'unit-guan-yu', url: guanYuUnitSheetUrl, actionKey: 'unit-guan-yu-actions', actionUrl: guanYuActionSheetUrl },
|
||||
{ key: 'unit-zhang-fei', url: zhangFeiUnitSheetUrl, actionKey: 'unit-zhang-fei-actions', actionUrl: zhangFeiActionSheetUrl },
|
||||
{ key: 'unit-rebel', url: rebelUnitSheetUrl, actionKey: 'unit-rebel-actions', actionUrl: rebelActionSheetUrl },
|
||||
{ key: 'unit-rebel-archer', url: rebelArcherUnitSheetUrl, actionKey: 'unit-rebel-archer-actions', actionUrl: rebelArcherActionSheetUrl },
|
||||
{ key: 'unit-rebel-cavalry', url: rebelCavalryUnitSheetUrl, actionKey: 'unit-rebel-cavalry-actions', actionUrl: rebelCavalryActionSheetUrl },
|
||||
{ key: 'unit-rebel-leader', url: rebelLeaderUnitSheetUrl, actionKey: 'unit-rebel-leader-actions', actionUrl: rebelLeaderActionSheetUrl },
|
||||
{ key: 'unit-lu-bu', url: luBuUnitSheetUrl, actionKey: 'unit-lu-bu-actions', actionUrl: luBuActionSheetUrl },
|
||||
{ key: 'unit-cao-cao', url: caoCaoUnitSheetUrl, actionKey: 'unit-cao-cao-actions', actionUrl: caoCaoActionSheetUrl },
|
||||
{ key: 'unit-lu-meng', url: luMengUnitSheetUrl, actionKey: 'unit-lu-meng-actions', actionUrl: luMengActionSheetUrl },
|
||||
{ key: 'unit-meng-huo', url: mengHuoUnitSheetUrl, actionKey: 'unit-meng-huo-actions', actionUrl: mengHuoActionSheetUrl },
|
||||
{ key: 'unit-zhao-yun', url: zhaoYunUnitSheetUrl, actionKey: 'unit-zhao-yun-actions', actionUrl: zhaoYunActionSheetUrl },
|
||||
{ key: 'unit-zhuge-liang', url: zhugeLiangUnitSheetUrl, actionKey: 'unit-zhuge-liang-actions', actionUrl: zhugeLiangActionSheetUrl },
|
||||
{ key: 'unit-jiang-wei', url: jiangWeiUnitSheetUrl, actionKey: 'unit-jiang-wei-actions', actionUrl: jiangWeiActionSheetUrl },
|
||||
{ key: 'unit-sima-yi', url: simaYiUnitSheetUrl, actionKey: 'unit-sima-yi-actions', actionUrl: simaYiActionSheetUrl },
|
||||
{ key: 'unit-ma-liang', url: maLiangUnitSheetUrl, actionKey: 'unit-ma-liang-actions', actionUrl: maLiangActionSheetUrl },
|
||||
{ key: 'unit-yi-ji', url: yiJiUnitSheetUrl, actionKey: 'unit-yi-ji-actions', actionUrl: yiJiActionSheetUrl },
|
||||
{ key: 'unit-gong-zhi', url: gongZhiUnitSheetUrl, actionKey: 'unit-gong-zhi-actions', actionUrl: gongZhiActionSheetUrl },
|
||||
{ key: 'unit-huang-zhong', url: huangZhongUnitSheetUrl, actionKey: 'unit-huang-zhong-actions', actionUrl: huangZhongActionSheetUrl },
|
||||
{ key: 'unit-wei-yan', url: weiYanUnitSheetUrl, actionKey: 'unit-wei-yan-actions', actionUrl: weiYanActionSheetUrl },
|
||||
{ key: 'unit-pang-tong', url: pangTongUnitSheetUrl, actionKey: 'unit-pang-tong-actions', actionUrl: pangTongActionSheetUrl },
|
||||
{ key: 'unit-fa-zheng', url: faZhengUnitSheetUrl, actionKey: 'unit-fa-zheng-actions', actionUrl: faZhengActionSheetUrl },
|
||||
{ key: 'unit-wu-yi', url: wuYiUnitSheetUrl, actionKey: 'unit-wu-yi-actions', actionUrl: wuYiActionSheetUrl },
|
||||
{ key: 'unit-yan-yan', url: yanYanUnitSheetUrl, actionKey: 'unit-yan-yan-actions', actionUrl: yanYanActionSheetUrl },
|
||||
{ key: 'unit-li-yan', url: liYanUnitSheetUrl, actionKey: 'unit-li-yan-actions', actionUrl: liYanActionSheetUrl },
|
||||
{ key: 'unit-huang-quan', url: huangQuanUnitSheetUrl, actionKey: 'unit-huang-quan-actions', actionUrl: huangQuanActionSheetUrl },
|
||||
{ key: 'unit-ma-chao', url: maChaoUnitSheetUrl, actionKey: 'unit-ma-chao-actions', actionUrl: maChaoActionSheetUrl },
|
||||
{ key: 'unit-ma-dai', url: maDaiUnitSheetUrl, actionKey: 'unit-ma-dai-actions', actionUrl: maDaiActionSheetUrl },
|
||||
{ key: 'unit-wang-ping', url: wangPingUnitSheetUrl, actionKey: 'unit-wang-ping-actions', actionUrl: wangPingActionSheetUrl },
|
||||
{ key: 'unit-shu-infantry', url: shuInfantryUnitSheetUrl, actionKey: 'unit-shu-infantry-actions', actionUrl: shuInfantryActionSheetUrl },
|
||||
{ key: 'unit-shu-spearman', url: shuSpearmanUnitSheetUrl, actionKey: 'unit-shu-spearman-actions', actionUrl: shuSpearmanActionSheetUrl },
|
||||
{ key: 'unit-shu-archer', url: shuArcherUnitSheetUrl, actionKey: 'unit-shu-archer-actions', actionUrl: shuArcherActionSheetUrl },
|
||||
{ key: 'unit-shu-cavalry', url: shuCavalryUnitSheetUrl, actionKey: 'unit-shu-cavalry-actions', actionUrl: shuCavalryActionSheetUrl },
|
||||
{ key: 'unit-shu-strategist', url: shuStrategistUnitSheetUrl, actionKey: 'unit-shu-strategist-actions', actionUrl: shuStrategistActionSheetUrl },
|
||||
{ key: 'unit-shu-officer', url: shuOfficerUnitSheetUrl, actionKey: 'unit-shu-officer-actions', actionUrl: shuOfficerActionSheetUrl },
|
||||
{ key: 'unit-wei-infantry', url: weiInfantryUnitSheetUrl, actionKey: 'unit-wei-infantry-actions', actionUrl: weiInfantryActionSheetUrl },
|
||||
{ key: 'unit-wei-archer', url: weiArcherUnitSheetUrl, actionKey: 'unit-wei-archer-actions', actionUrl: weiArcherActionSheetUrl },
|
||||
{ key: 'unit-wei-cavalry', url: weiCavalryUnitSheetUrl, actionKey: 'unit-wei-cavalry-actions', actionUrl: weiCavalryActionSheetUrl },
|
||||
{ key: 'unit-wei-strategist', url: weiStrategistUnitSheetUrl, actionKey: 'unit-wei-strategist-actions', actionUrl: weiStrategistActionSheetUrl },
|
||||
{ key: 'unit-wei-officer', url: weiOfficerUnitSheetUrl, actionKey: 'unit-wei-officer-actions', actionUrl: weiOfficerActionSheetUrl },
|
||||
{ key: 'unit-wu-infantry', url: wuInfantryUnitSheetUrl, actionKey: 'unit-wu-infantry-actions', actionUrl: wuInfantryActionSheetUrl },
|
||||
{ key: 'unit-wu-archer', url: wuArcherUnitSheetUrl, actionKey: 'unit-wu-archer-actions', actionUrl: wuArcherActionSheetUrl },
|
||||
{ key: 'unit-wu-cavalry', url: wuCavalryUnitSheetUrl, actionKey: 'unit-wu-cavalry-actions', actionUrl: wuCavalryActionSheetUrl },
|
||||
{ key: 'unit-wu-strategist', url: wuStrategistUnitSheetUrl, actionKey: 'unit-wu-strategist-actions', actionUrl: wuStrategistActionSheetUrl },
|
||||
{ key: 'unit-wu-officer', url: wuOfficerUnitSheetUrl, actionKey: 'unit-wu-officer-actions', actionUrl: wuOfficerActionSheetUrl },
|
||||
{ key: 'unit-nanman-infantry', url: nanmanInfantryUnitSheetUrl, actionKey: 'unit-nanman-infantry-actions', actionUrl: nanmanInfantryActionSheetUrl },
|
||||
{ key: 'unit-nanman-shaman', url: nanmanShamanUnitSheetUrl, actionKey: 'unit-nanman-shaman-actions', actionUrl: nanmanShamanActionSheetUrl },
|
||||
{ key: 'unit-nanman-officer', url: nanmanOfficerUnitSheetUrl, actionKey: 'unit-nanman-officer-actions', actionUrl: nanmanOfficerActionSheetUrl }
|
||||
];
|
||||
type UnitSheetAsset = {
|
||||
key: string;
|
||||
url: string;
|
||||
actionKey: string;
|
||||
actionUrl: string;
|
||||
};
|
||||
|
||||
const unitSheetModules = import.meta.glob('../../assets/images/units/unit-*.png', {
|
||||
eager: true,
|
||||
import: 'default'
|
||||
}) as Record<string, string>;
|
||||
|
||||
function textureKeyFromPath(path: string) {
|
||||
const fileName = path.split('/').pop() ?? path;
|
||||
return fileName.replace(/\.png$/i, '');
|
||||
}
|
||||
|
||||
const unitSheetUrls = Object.fromEntries(
|
||||
Object.entries(unitSheetModules).map(([path, url]) => [textureKeyFromPath(path), url])
|
||||
);
|
||||
|
||||
const sourceUnitSheets: UnitSheetAsset[] = Object.entries(unitSheetUrls)
|
||||
.filter(([key]) => !key.endsWith('-actions'))
|
||||
.map(([key, url]) => ({
|
||||
key,
|
||||
url,
|
||||
actionKey: `${key}-actions`,
|
||||
actionUrl: unitSheetUrls[`${key}-actions`]
|
||||
}))
|
||||
.filter((sheet): sheet is UnitSheetAsset => Boolean(sheet.actionUrl))
|
||||
.sort((left, right) => left.key.localeCompare(right.key));
|
||||
|
||||
const animatedUnitSheets = sourceUnitSheets.map(({ key }) => ({ key }));
|
||||
|
||||
@@ -179,13 +58,7 @@ export class BootScene extends Phaser.Scene {
|
||||
preload() {
|
||||
this.load.image('title-taoyuan', titleBackgroundUrl);
|
||||
this.load.image('story-militia', storyBackgroundAssets['story-militia']);
|
||||
this.load.image('portrait-liu-bei', liuBeiPortraitUrl);
|
||||
this.load.image('portrait-guan-yu', guanYuPortraitUrl);
|
||||
this.load.image('portrait-zhang-fei', zhangFeiPortraitUrl);
|
||||
this.load.image('portrait-zhuge-liang', zhugeLiangPortraitUrl);
|
||||
this.load.image('portrait-zhao-yun', zhaoYunPortraitUrl);
|
||||
this.load.image('portrait-jiang-wei', jiangWeiPortraitUrl);
|
||||
this.load.image('portrait-sima-yi', simaYiPortraitUrl);
|
||||
Object.entries(portraitAssets).forEach(([key, url]) => this.load.image(`portrait-${key}`, url));
|
||||
|
||||
sourceUnitSheets.forEach(({ key, url, actionKey, actionUrl }) => {
|
||||
this.load.spritesheet(key, url, {
|
||||
|
||||
@@ -1,29 +1,10 @@
|
||||
import Phaser from 'phaser';
|
||||
import { soundDirector } from '../audio/SoundDirector';
|
||||
import { storyBackgroundAssets } from '../data/storyAssets';
|
||||
import { portraitKeyForSpeaker, portraitTextureKey } from '../data/portraitAssets';
|
||||
import { storyBackgroundAssets, storyBackgroundKeysFor } from '../data/storyAssets';
|
||||
import { prologuePages, type PortraitKey, type StoryPage } from '../data/scenario';
|
||||
import { palette } from '../ui/palette';
|
||||
|
||||
const portraitKeys: Record<PortraitKey, string> = {
|
||||
liuBei: 'portrait-liu-bei',
|
||||
guanYu: 'portrait-guan-yu',
|
||||
zhangFei: 'portrait-zhang-fei',
|
||||
zhugeLiang: 'portrait-zhuge-liang',
|
||||
zhaoYun: 'portrait-zhao-yun',
|
||||
jiangWei: 'portrait-jiang-wei',
|
||||
simaYi: 'portrait-sima-yi'
|
||||
};
|
||||
|
||||
const portraitBySpeaker: Record<string, PortraitKey> = {
|
||||
유비: 'liuBei',
|
||||
관우: 'guanYu',
|
||||
장비: 'zhangFei',
|
||||
제갈량: 'zhugeLiang',
|
||||
조운: 'zhaoYun',
|
||||
강유: 'jiangWei',
|
||||
사마의: 'simaYi'
|
||||
};
|
||||
|
||||
type AlphaObject = Phaser.GameObjects.Image | Phaser.GameObjects.Rectangle | Phaser.GameObjects.Text;
|
||||
|
||||
type StorySceneData = {
|
||||
@@ -69,8 +50,9 @@ export class StoryScene extends Phaser.Scene {
|
||||
pageIndex: this.pageIndex,
|
||||
totalPages: this.pages.length,
|
||||
currentPageId: page?.id,
|
||||
background: page?.background,
|
||||
backgroundReady: page ? this.textures.exists(page.background) : false,
|
||||
requestedBackground: page?.background,
|
||||
background: page ? this.pageBackgroundKey(page) : undefined,
|
||||
backgroundReady: page ? this.textures.exists(this.pageBackgroundKey(page)) : false,
|
||||
activeTexture: this.background?.texture.key ?? null,
|
||||
portraitKey: page ? this.pagePortraitKey(page) ?? null : null,
|
||||
portraitTextureKey: this.portrait?.visible ? this.portrait.texture.key : null
|
||||
@@ -93,7 +75,7 @@ export class StoryScene extends Phaser.Scene {
|
||||
this.ensureStoryBackgroundsLoaded(() => {
|
||||
this.loadingText?.destroy();
|
||||
this.loadingText = undefined;
|
||||
this.background = this.add.image(width / 2, height / 2, this.resolveBackgroundKey(this.pages[0].background));
|
||||
this.background = this.add.image(width / 2, height / 2, this.resolveBackgroundKey(this.pageBackgroundKey(this.pages[0])));
|
||||
this.drawSceneShade(width, height);
|
||||
this.drawDialogPanel(width, height);
|
||||
this.showPage(0, true);
|
||||
@@ -105,7 +87,8 @@ export class StoryScene extends Phaser.Scene {
|
||||
}
|
||||
|
||||
private ensureStoryBackgroundsLoaded(onReady: () => void) {
|
||||
const missingKeys = Array.from(new Set(this.pages.map((page) => page.background))).filter((key) => !this.textures.exists(key));
|
||||
const requestedKeys = this.pages.flatMap((page) => storyBackgroundKeysFor(page.background));
|
||||
const missingKeys = Array.from(new Set(requestedKeys)).filter((key) => !this.textures.exists(key));
|
||||
const loadableKeys = missingKeys.filter((key) => storyBackgroundAssets[key]);
|
||||
|
||||
missingKeys
|
||||
@@ -252,16 +235,17 @@ export class StoryScene extends Phaser.Scene {
|
||||
|
||||
private applyPage(page: StoryPage) {
|
||||
soundDirector.playMusic(page.bgm);
|
||||
this.applyBackground(page.background);
|
||||
this.applyBackground(page);
|
||||
this.chapterText?.setText(page.chapter);
|
||||
|
||||
const portraitKey = this.pagePortraitKey(page);
|
||||
const textureKey = portraitKey ? portraitTextureKey(portraitKey) : undefined;
|
||||
|
||||
if (portraitKey) {
|
||||
if (textureKey && this.textures.exists(textureKey)) {
|
||||
this.portraitFrame?.setVisible(true);
|
||||
this.portrait?.setVisible(true);
|
||||
this.portraitDivider?.setVisible(true);
|
||||
this.portrait?.setTexture(portraitKeys[portraitKey]);
|
||||
this.portrait?.setTexture(textureKey);
|
||||
this.portrait?.setDisplaySize(128, 128);
|
||||
this.nameText?.setPosition(270, this.scale.height - 210);
|
||||
this.bodyText?.setPosition(270, this.scale.height - 160);
|
||||
@@ -281,16 +265,25 @@ export class StoryScene extends Phaser.Scene {
|
||||
}
|
||||
|
||||
private pagePortraitKey(page: StoryPage) {
|
||||
return page.portrait ?? (page.speaker ? portraitBySpeaker[page.speaker] : undefined);
|
||||
return page.portrait ?? portraitKeyForSpeaker(page.speaker);
|
||||
}
|
||||
|
||||
private applyBackground(textureKey: string) {
|
||||
private pageBackgroundKey(page: StoryPage) {
|
||||
const keys = storyBackgroundKeysFor(page.background).filter((key) => this.textures.exists(key));
|
||||
if (keys.length <= 1) {
|
||||
return keys[0] ?? page.background;
|
||||
}
|
||||
|
||||
return keys[this.hashString(page.id) % keys.length];
|
||||
}
|
||||
|
||||
private applyBackground(page: StoryPage) {
|
||||
if (!this.background) {
|
||||
return;
|
||||
}
|
||||
|
||||
const { width, height } = this.scale;
|
||||
const safeTextureKey = this.resolveBackgroundKey(textureKey);
|
||||
const safeTextureKey = this.resolveBackgroundKey(this.pageBackgroundKey(page));
|
||||
const source = this.textures.get(safeTextureKey).getSourceImage();
|
||||
const imageWidth = source.width;
|
||||
const imageHeight = source.height;
|
||||
@@ -318,6 +311,14 @@ export class StoryScene extends Phaser.Scene {
|
||||
return this.textures.exists(textureKey) ? textureKey : 'story-fallback';
|
||||
}
|
||||
|
||||
private hashString(value: string) {
|
||||
let hash = 0;
|
||||
for (let index = 0; index < value.length; index += 1) {
|
||||
hash = (hash * 31 + value.charCodeAt(index)) >>> 0;
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
private advance() {
|
||||
if (this.transitioning) {
|
||||
return;
|
||||
|
||||