fix: polish bark battle creation flow

This commit is contained in:
kdletters
2026-05-22 05:00:07 +08:00
parent 01da85a577
commit bf82f04b64
73 changed files with 9362 additions and 2663 deletions

View File

@@ -1,3 +1,4 @@
import type { BarkBattleWorkSummary } from '../../../packages/shared/src/contracts/barkBattle';
import type { BigFishWorkSummary } from '../../../packages/shared/src/contracts/bigFishWorkSummary';
import type { BabyObjectMatchDraft } from '../../../packages/shared/src/contracts/edutainmentBabyObject';
import { BABY_OBJECT_MATCH_EDUTAINMENT_TAG } from '../../../packages/shared/src/contracts/edutainmentBabyObject';
@@ -22,6 +23,7 @@ import { buildCustomWorldPlayableCharacters } from '../../data/characterPresets'
import { resolveCustomWorldCampSceneImage } from '../../data/customWorldVisuals';
import {
buildBabyObjectMatchPublicWorkCode,
buildBarkBattlePublicWorkCode,
buildBigFishPublicWorkCode,
buildMatch3DPublicWorkCode,
buildPuzzlePublicWorkCode,
@@ -43,6 +45,7 @@ export type PlatformWorldCardLike =
| PlatformSquareHoleGalleryCard
| PlatformPuzzleGalleryCard
| PlatformVisualNovelGalleryCard
| PlatformBarkBattleGalleryCard
| PlatformEdutainmentGalleryCard;
export type PlatformPuzzleGalleryCard = {
@@ -196,6 +199,34 @@ export type PlatformEdutainmentGalleryCard = {
updatedAt: string;
};
export type PlatformBarkBattleGalleryCard = {
sourceType: 'bark-battle';
workId: string;
profileId: string;
sourceSessionId?: string | null;
publicWorkCode: string;
ownerUserId: string;
authorPublicUserCode: string | null;
authorDisplayName: string;
worldName: string;
subtitle: string;
summaryText: string;
coverImageSrc: string | null;
coverRenderMode: 'image' | 'scene_with_roles';
coverCharacterImageSrcs: string[];
themeTags: string[];
themeMode: CustomWorldGalleryCard['themeMode'];
playableNpcCount: number;
landmarkCount: number;
playCount?: number;
remixCount?: number;
likeCount?: number;
recentPlayCount7d?: number;
visibility: 'published';
publishedAt: string | null;
updatedAt: string;
};
export type PlatformPublicGalleryCard =
| CustomWorldGalleryCard
| PlatformBigFishGalleryCard
@@ -203,6 +234,7 @@ export type PlatformPublicGalleryCard =
| PlatformSquareHoleGalleryCard
| PlatformPuzzleGalleryCard
| PlatformVisualNovelGalleryCard
| PlatformBarkBattleGalleryCard
| PlatformEdutainmentGalleryCard;
export function isLibraryWorldEntry(
@@ -247,6 +279,12 @@ export function isEdutainmentGalleryEntry(
return 'sourceType' in entry && entry.sourceType === 'edutainment';
}
export function isBarkBattleGalleryEntry(
entry: PlatformWorldCardLike,
): entry is PlatformBarkBattleGalleryCard {
return 'sourceType' in entry && entry.sourceType === 'bark-battle';
}
export function mapPuzzleWorkToPlatformGalleryCard(
work: PuzzleWorkSummary,
): PlatformPuzzleGalleryCard {
@@ -422,6 +460,64 @@ export function mapBabyObjectMatchDraftToPlatformGalleryCard(
};
}
export function mapBarkBattleWorkToPlatformGalleryCard(
work: BarkBattleWorkSummary,
): PlatformBarkBattleGalleryCard {
const playerCharacterImageSrc = normalizePlatformOptionalImageSrc(
work.playerCharacterImageSrc,
);
const opponentCharacterImageSrc = normalizePlatformOptionalImageSrc(
work.opponentCharacterImageSrc,
);
const backgroundImageSrc = normalizePlatformOptionalImageSrc(
work.uiBackgroundImageSrc,
);
const coverImageSrc =
backgroundImageSrc ??
playerCharacterImageSrc ??
opponentCharacterImageSrc ??
'/creation-type-references/bark-battle.webp';
const coverCharacterImageSrcs = [
playerCharacterImageSrc,
opponentCharacterImageSrc,
].filter((imageSrc): imageSrc is string => Boolean(imageSrc));
const canRenderSceneWithRoles =
Boolean(backgroundImageSrc) && coverCharacterImageSrcs.length >= 2;
return {
sourceType: 'bark-battle',
workId: work.workId,
profileId: work.workId,
sourceSessionId: work.draftId ?? null,
publicWorkCode: buildBarkBattlePublicWorkCode(work.workId),
ownerUserId: work.ownerUserId,
authorPublicUserCode: null,
authorDisplayName: work.authorDisplayName,
worldName: work.title.trim() || '汪汪声浪大作战',
subtitle: `汪汪声浪 · ${describeBarkBattleDifficultyLabel(
work.difficultyPreset,
)}`,
summaryText:
work.summary.trim() ||
work.themeDescription.trim() ||
'用声音能量挑战对手。',
coverImageSrc,
coverRenderMode: canRenderSceneWithRoles ? 'scene_with_roles' : 'image',
coverCharacterImageSrcs,
themeTags: buildBarkBattleThemeTags(work),
themeMode: 'martial',
playableNpcCount: 0,
landmarkCount: 0,
playCount: work.playCount ?? 0,
remixCount: 0,
likeCount: 0,
recentPlayCount7d: work.recentPlayCount7d ?? 0,
visibility: 'published',
publishedAt: work.publishedAt ?? null,
updatedAt: work.updatedAt,
};
}
export function resolvePlatformWorldStats(entry: PlatformWorldCardLike) {
return {
playCount: 'playCount' in entry ? (entry.playCount ?? 0) : 0,
@@ -473,6 +569,10 @@ export function resolvePlatformWorldFallbackCoverImage(
return '/creation-type-references/creative-agent.webp';
}
if (isBarkBattleGalleryEntry(entry)) {
return '/creation-type-references/bark-battle.webp';
}
return '/creation-type-references/rpg.webp';
}
@@ -634,6 +734,12 @@ export function buildPlatformWorldTags(entry: PlatformWorldCardLike) {
: [entry.templateName];
}
if (isBarkBattleGalleryEntry(entry)) {
return entry.themeTags.length > 0
? entry.themeTags.slice(0, 3)
: ['汪汪声浪'];
}
if (!isLibraryWorldEntry(entry)) {
return [
describePlatformThemeLabel(entry.themeMode),
@@ -724,6 +830,10 @@ export function resolvePlatformPublicWorkCode(
return entry.publicWorkCode;
}
if (isBarkBattleGalleryEntry(entry)) {
return entry.publicWorkCode;
}
return entry.publicWorkCode;
}
@@ -745,3 +855,31 @@ export function describePlatformThemeLabel(
return '回响';
}
}
function normalizePlatformOptionalImageSrc(value?: string | null) {
return value?.trim() || null;
}
function describeBarkBattleDifficultyLabel(
difficulty: BarkBattleWorkSummary['difficultyPreset'],
) {
switch (difficulty) {
case 'easy':
return '轻松';
case 'hard':
return '高能';
default:
return '普通';
}
}
function buildBarkBattleThemeTags(work: BarkBattleWorkSummary) {
return [
'汪汪声浪',
describeBarkBattleDifficultyLabel(work.difficultyPreset),
work.themeDescription,
]
.map((tag) => tag.trim())
.filter(Boolean)
.slice(0, 3);
}