feat: add gift zone city team and mashup mode

This commit is contained in:
manpengan
2026-03-29 00:49:03 +08:00
parent 5c2b4f40f9
commit 92bf1f5070
12 changed files with 862 additions and 60 deletions

View File

@@ -1,4 +1,5 @@
import { createGameSession } from '../gameplay/session/index.js'
import { generateMashupBoard } from '../gameplay/difficulty/index.js'
function createHomeSelectScene() {
return {
@@ -20,6 +21,41 @@ function createGiftZoneScene(selectedTab = 'magnets') {
}
}
function createCityTeamSelectScene(options, selectedTeamCityId) {
return {
type: 'city-team-select',
options,
selectedTeamCityId,
}
}
function createGameplayScene({
cityId,
levelId,
session,
mode = 'city',
title = null,
subtitle = null,
accentColor = null,
sourceCityIds = [],
elementDefinitions = [],
rewardSummary = null,
}) {
return {
type: 'gameplay',
cityId,
levelId,
session,
mode,
title,
subtitle,
accentColor,
sourceCityIds,
elementDefinitions,
rewardSummary,
}
}
function createLevelProgress(city, playerState) {
const cityProgress = playerState.levelProgress[city.id] ?? {}
@@ -57,6 +93,35 @@ function createAcquiredDate(now) {
return new Date(now()).toISOString()
}
function createCityTeamOptions(contentSystem, playerState) {
return playerState.unlockedCities
.map((cityId) => {
const city = contentSystem.getCity(cityId)
if (!city) {
return null
}
return {
cityId: city.id,
cityName: city.display.name,
catName: city.cat.name,
themeColor: city.display.bgColor,
}
})
.filter(Boolean)
}
function createMashupRewardCandidates(contentSystem, playerState) {
return playerState.unlockedCities
.map((cityId) => contentSystem.getCity(cityId))
.filter(Boolean)
.flatMap((city) => city.levelPresets.map((preset) => ({
magnetId: `magnet_${city.id}_${preset.id}`,
cityId: city.id,
levelId: preset.id,
})))
}
function awardLevelMagnet(playerState, cityId, levelId, now) {
const magnets = ensureCollection(playerState, 'collectedMagnets')
const magnetId = `magnet_${cityId}_${levelId}`
@@ -73,6 +138,41 @@ function awardLevelMagnet(playerState, cityId, levelId, now) {
})
}
function awardMashupReward(contentSystem, playerState, seed, now) {
const rewardCandidates = createMashupRewardCandidates(contentSystem, playerState)
const magnets = ensureCollection(playerState, 'collectedMagnets')
if (rewardCandidates.length === 0) {
playerState.inventory.shuffle = (playerState.inventory.shuffle ?? 0) + 1
return {
type: 'inventory',
itemId: 'shuffle',
amount: 1,
}
}
const reward = rewardCandidates[seed % rewardCandidates.length]
if (magnets.some((entry) => entry.magnetId === reward.magnetId)) {
playerState.inventory.shuffle = (playerState.inventory.shuffle ?? 0) + 1
return {
type: 'inventory',
itemId: 'shuffle',
amount: 1,
}
}
magnets.push({
...reward,
acquiredDate: createAcquiredDate(now),
})
return {
type: 'magnet',
...reward,
}
}
function markCityCompletion(contentSystem, playerState, cityId, now) {
if (!playerState.collectedCats.includes(cityId)) {
playerState.collectedCats.push(cityId)
@@ -105,6 +205,40 @@ export function createSceneStore({ contentSystem, playerState, now = () => Date.
const history = []
let currentScene = createHomeSelectScene()
function createMashupSession(seed = now()) {
const boardState = generateMashupBoard({
cityIds: playerState.unlockedCities,
seed,
contentSystem,
})
return createGameSession({
cityId: 'mashup',
levelId: 1,
seed,
contentSystem,
boardState,
restartFactory: (nextSeed) => createMashupSession(nextSeed),
})
}
function createMashupGameplayScene(seed = now()) {
const session = createMashupSession(seed)
const boardState = session.getBoardState()
return createGameplayScene({
cityId: 'mashup',
levelId: 1,
session,
mode: 'mashup',
title: '主题大混战',
subtitle: `${boardState.sourceCityIds.length} 城混搭`,
accentColor: '#E74C3C',
sourceCityIds: boardState.sourceCityIds ?? [],
elementDefinitions: boardState.elementDefinitions ?? [],
})
}
function getScene() {
return currentScene
}
@@ -130,7 +264,9 @@ export function createSceneStore({ contentSystem, playerState, now = () => Date.
}
if (tile.id === 'mashup') {
return { opened: false, reason: 'mode-unavailable' }
history.push(currentScene)
currentScene = createMashupGameplayScene()
return { opened: true }
}
return { opened: false, reason: 'unavailable' }
@@ -167,7 +303,7 @@ export function createSceneStore({ contentSystem, playerState, now = () => Date.
return false
}
if (!['magnets', 'stamps'].includes(tabId)) {
if (!['magnets', 'stamps', 'cats'].includes(tabId)) {
return false
}
@@ -179,6 +315,40 @@ export function createSceneStore({ contentSystem, playerState, now = () => Date.
return true
}
function openCityTeamSelect() {
history.push(currentScene)
currentScene = createCityTeamSelectScene(
createCityTeamOptions(contentSystem, playerState),
playerState.cityTeam?.teamCityId ?? null,
)
return true
}
function chooseCityTeam(cityId) {
if (currentScene.type !== 'city-team-select') {
return false
}
if (playerState.cityTeam?.teamCityId) {
return false
}
if (!playerState.unlockedCities.includes(cityId)) {
return false
}
playerState.cityTeam = {
...playerState.cityTeam,
teamCityId: cityId,
joinedDate: createAcquiredDate(now),
lastSwitchDate: null,
}
currentScene = history.pop() ?? createHomeSelectScene()
return true
}
function openLevel(cityId, levelId) {
if (!playerState.unlockedCities.includes(cityId)) {
return false
@@ -197,8 +367,7 @@ export function createSceneStore({ contentSystem, playerState, now = () => Date.
}
history.push(currentScene)
currentScene = {
type: 'gameplay',
currentScene = createGameplayScene({
cityId,
levelId,
session: createGameSession({
@@ -206,7 +375,7 @@ export function createSceneStore({ contentSystem, playerState, now = () => Date.
levelId,
contentSystem,
}),
}
})
return true
}
@@ -246,6 +415,33 @@ export function createSceneStore({ contentSystem, playerState, now = () => Date.
return true
}
function completeGameplayVictory(stars = 3) {
if (currentScene.type !== 'gameplay') {
return null
}
if (currentScene.mode === 'mashup') {
const reward = awardMashupReward(contentSystem, playerState, currentScene.session.seed, now)
currentScene = {
...currentScene,
rewardSummary: reward,
}
return reward
}
completeLevel({
cityId: currentScene.cityId,
levelId: currentScene.levelId,
stars,
})
return {
type: 'city',
cityId: currentScene.cityId,
levelId: currentScene.levelId,
}
}
function goBack() {
if (history.length === 0) {
currentScene = createHomeSelectScene()
@@ -274,8 +470,11 @@ export function createSceneStore({ contentSystem, playerState, now = () => Date.
openCity,
openGiftZone,
selectGiftTab,
openCityTeamSelect,
chooseCityTeam,
openLevel,
completeLevel,
completeGameplayVictory,
restartCurrentLevel,
goBack,
}