287 lines
8.4 KiB
JavaScript
287 lines
8.4 KiB
JavaScript
import { continents } from './continents/index.js'
|
|
import cities from './cities/index.js'
|
|
import { createDefaultPlayerState } from './default-player-state.js'
|
|
import { getCatalogChildren } from './navigation/world-catalog.js'
|
|
import { getEnabledRoots, getNavNode as getRuntimeNavNode } from './navigation/runtime-nav.js'
|
|
import { createBundleResolver } from './registry/bundle-resolver.js'
|
|
import { createCityRegistry } from './registry/city-registry.js'
|
|
import { createContinentRegistry } from './registry/continent-registry.js'
|
|
import { projectCityCardView, projectCityProgress } from './registry/progress-projector.js'
|
|
import { validateAssets } from './validation/validate-assets.js'
|
|
import { validateCity } from './validation/validate-city.js'
|
|
import { validateContinent } from './validation/validate-continent.js'
|
|
|
|
export function validateContent(options = {}) {
|
|
const continentIds = new Set(continents.map((continent) => continent.id))
|
|
const cityIds = new Set(cities.map((city) => city.id))
|
|
const errors = []
|
|
const warnings = []
|
|
|
|
for (const continent of continents) {
|
|
const result = validateContinent(continent, cityIds)
|
|
errors.push(...result.errors)
|
|
warnings.push(...result.warnings)
|
|
}
|
|
|
|
for (const city of cities) {
|
|
const result = validateCity(city, continentIds, cityIds)
|
|
errors.push(...result.errors)
|
|
warnings.push(...result.warnings)
|
|
|
|
const assetResult = validateAssets(city, options.assetExists)
|
|
errors.push(...assetResult.errors)
|
|
warnings.push(...assetResult.warnings)
|
|
}
|
|
|
|
return { errors, warnings }
|
|
}
|
|
|
|
export function createContentSystem() {
|
|
const continentRegistry = createContinentRegistry(continents)
|
|
const cityRegistry = createCityRegistry(cities)
|
|
const bundleResolver = createBundleResolver(cityRegistry)
|
|
const homeRootNodes = getCatalogChildren(null)
|
|
const giftAlbumDefinitions = [
|
|
{ id: 'magnets', name: '冰箱贴册' },
|
|
{ id: 'stamps', name: '邮票册' },
|
|
{ id: 'cats', name: '猫猫册' },
|
|
]
|
|
|
|
function getCompletedCityCount(playerState) {
|
|
return cityRegistry
|
|
.getAllCities()
|
|
.filter((city) => projectCityProgress(city, playerState).isCompleted)
|
|
.length
|
|
}
|
|
|
|
function projectNavNode(node) {
|
|
if (!node) {
|
|
return null
|
|
}
|
|
|
|
return {
|
|
...node,
|
|
isUnlocked: node.isUnlockedByDefault,
|
|
}
|
|
}
|
|
|
|
function projectHomeTile(node, playerState) {
|
|
const completedCityCount = getCompletedCityCount(playerState)
|
|
const isMashupTile = node.id === 'mashup'
|
|
const isComingSoonTile = node.id === 'coming_soon'
|
|
const isUnlocked = isMashupTile
|
|
? completedCityCount >= 2
|
|
: node.status === 'active' && node.isUnlockedByDefault
|
|
const isInteractive = isComingSoonTile || isMashupTile || node.type === 'continent'
|
|
|
|
return {
|
|
...node,
|
|
isUnlocked,
|
|
isInteractive,
|
|
isSpecial: isMashupTile || isComingSoonTile,
|
|
}
|
|
}
|
|
|
|
function getMagnetEntries(playerState) {
|
|
const magnets = playerState.collectedMagnets ?? []
|
|
|
|
return cityRegistry.getAllCities().map((city) => {
|
|
const collectedCount = magnets.filter((entry) => entry.cityId === city.id).length
|
|
|
|
return {
|
|
cityId: city.id,
|
|
cityName: city.display.name,
|
|
cityNameEn: city.display.nameEn,
|
|
themeColor: city.display.bgColor,
|
|
collectedCount,
|
|
totalCount: city.levelPresets.length,
|
|
}
|
|
})
|
|
}
|
|
|
|
function getStampEntries(playerState) {
|
|
const stamps = playerState.collectedStamps ?? []
|
|
|
|
return cityRegistry.getAllCities().map((city) => {
|
|
const stampId = city.passport?.stampId ?? `stamp_${city.id}`
|
|
|
|
return {
|
|
cityId: city.id,
|
|
cityName: city.display.name,
|
|
cityNameEn: city.display.nameEn,
|
|
themeColor: city.display.bgColor,
|
|
stampId,
|
|
isCollected: stamps.some((entry) => entry.stampId === stampId),
|
|
}
|
|
})
|
|
}
|
|
|
|
function getCatEntries(playerState) {
|
|
const collectedCats = new Set(playerState.collectedCats ?? [])
|
|
|
|
return cityRegistry.getAllCities().map((city) => ({
|
|
cityId: city.id,
|
|
cityName: city.display.name,
|
|
cityNameEn: city.display.nameEn,
|
|
themeColor: city.display.bgColor,
|
|
catId: city.cat.id,
|
|
catName: city.cat.name,
|
|
catThumb: city.cover.catThumb,
|
|
isCollected: collectedCats.has(city.id),
|
|
}))
|
|
}
|
|
|
|
function summarizeAlbumEntries(albumId, entries) {
|
|
if (albumId === 'magnets') {
|
|
return {
|
|
collectedCount: entries.reduce((sum, entry) => sum + entry.collectedCount, 0),
|
|
totalCount: entries.reduce((sum, entry) => sum + entry.totalCount, 0),
|
|
}
|
|
}
|
|
|
|
return {
|
|
collectedCount: entries.filter((entry) => entry.isCollected).length,
|
|
totalCount: entries.length,
|
|
}
|
|
}
|
|
|
|
function getGiftAlbumEntries(albumId, playerState) {
|
|
if (albumId === 'magnets') {
|
|
return getMagnetEntries(playerState)
|
|
}
|
|
|
|
if (albumId === 'stamps') {
|
|
return getStampEntries(playerState)
|
|
}
|
|
|
|
if (albumId === 'cats') {
|
|
return getCatEntries(playerState)
|
|
}
|
|
|
|
return []
|
|
}
|
|
|
|
function getGiftAlbums(playerState) {
|
|
return giftAlbumDefinitions.map((definition) => {
|
|
const entries = getGiftAlbumEntries(definition.id, playerState)
|
|
const summary = summarizeAlbumEntries(definition.id, entries)
|
|
|
|
return {
|
|
...definition,
|
|
collectedCount: summary.collectedCount,
|
|
totalCount: summary.totalCount,
|
|
}
|
|
})
|
|
}
|
|
|
|
function projectCityNavEntry(cityId, parentId, playerState) {
|
|
const city = cityRegistry.getCity(cityId)
|
|
if (!city) {
|
|
return null
|
|
}
|
|
|
|
return {
|
|
...projectCityCardView(city, playerState),
|
|
id: city.id,
|
|
type: 'city',
|
|
parentId,
|
|
themeColor: city.display.bgColor,
|
|
childType: null,
|
|
childIds: [],
|
|
pageSize: 0,
|
|
status: 'active',
|
|
isUnlockedByDefault: cityId === 'beijing',
|
|
sortOrder: city.sortOrder,
|
|
}
|
|
}
|
|
|
|
function getNavChildren(nodeId, playerState) {
|
|
const node = getRuntimeNavNode(nodeId)
|
|
if (!node || !node.childType) {
|
|
return []
|
|
}
|
|
|
|
if (node.childType === 'city') {
|
|
return node.childIds
|
|
.map((cityId) => projectCityNavEntry(cityId, node.id, playerState))
|
|
.filter(Boolean)
|
|
}
|
|
|
|
return node.childIds
|
|
.map((childId) => projectNavNode(getRuntimeNavNode(childId)))
|
|
.filter(Boolean)
|
|
}
|
|
|
|
return {
|
|
getContinentList() {
|
|
return continentRegistry.getContinentList()
|
|
},
|
|
getContinent(continentId) {
|
|
return continentRegistry.getContinent(continentId)
|
|
},
|
|
getCity(cityId) {
|
|
return cityRegistry.getCity(cityId)
|
|
},
|
|
listCitiesByContinent(continentId) {
|
|
return cityRegistry.listCitiesByContinent(continentId)
|
|
},
|
|
getLevelPreset(cityId, levelId) {
|
|
return cityRegistry.getLevelPreset(cityId, levelId)
|
|
},
|
|
getCityProgress(cityId, playerState) {
|
|
const city = cityRegistry.getCity(cityId)
|
|
return city ? projectCityProgress(city, playerState) : null
|
|
},
|
|
getCityCardView(cityId, playerState) {
|
|
const city = cityRegistry.getCity(cityId)
|
|
return city ? projectCityCardView(city, playerState) : null
|
|
},
|
|
listCityCards(continentId, playerState) {
|
|
const navChildren = getNavChildren(continentId, playerState)
|
|
if (navChildren.length > 0) {
|
|
return navChildren.filter((entry) => entry.type === 'city')
|
|
}
|
|
|
|
return cityRegistry
|
|
.listCitiesByContinent(continentId)
|
|
.map((city) => projectCityCardView(city, playerState))
|
|
},
|
|
getRootNavNodes() {
|
|
return getEnabledRoots().map((node) => projectNavNode(node))
|
|
},
|
|
getHomeTiles(playerState) {
|
|
return homeRootNodes.map((node) => projectHomeTile(node, playerState))
|
|
},
|
|
getHomeTile(tileId, playerState) {
|
|
const node = homeRootNodes.find((entry) => entry.id === tileId) ?? null
|
|
return node ? projectHomeTile(node, playerState) : null
|
|
},
|
|
getGiftAlbums(playerState) {
|
|
return getGiftAlbums(playerState)
|
|
},
|
|
getGiftAlbumEntries(albumId, playerState) {
|
|
return getGiftAlbumEntries(albumId, playerState)
|
|
},
|
|
getNavNode(nodeId) {
|
|
return projectNavNode(getRuntimeNavNode(nodeId))
|
|
},
|
|
getNavChildren(nodeId, playerState) {
|
|
return getNavChildren(nodeId, playerState)
|
|
},
|
|
ensureCityBundle(cityId) {
|
|
return bundleResolver.ensureCityBundle(cityId)
|
|
},
|
|
validateContent(options) {
|
|
return validateContent(options)
|
|
},
|
|
}
|
|
}
|
|
|
|
export {
|
|
continents,
|
|
cities,
|
|
createDefaultPlayerState,
|
|
}
|
|
|
|
export default createContentSystem
|