use crate::puzzle::{PuzzleGalleryCardViewRow, puzzle_gallery_card_view, puzzle_gallery_view}; use crate::*; use module_custom_world::{CustomWorldGalleryEntrySnapshot, CustomWorldProfileSnapshot}; use module_puzzle::PuzzleWorkProfile; use spacetimedb::AnonymousViewContext; /// 跨玩法公开作品列表卡片读模型。 /// /// 该 view 只收口平台公开列表所需字段;玩法专属 runtime 配置仍留在各玩法详情 / /// runtime procedure 中读取。 #[spacetimedb::view(accessor = public_work_gallery_entry, public)] pub fn public_work_gallery_entry(ctx: &AnonymousViewContext) -> Vec { let mut entries = Vec::new(); entries.extend( puzzle_gallery_card_view(ctx) .into_iter() .map(map_puzzle_gallery_entry), ); entries.extend( custom_world_public_gallery_snapshots(ctx) .into_iter() .map(map_custom_world_gallery_entry), ); entries.extend( jump_hop_gallery_card_view(ctx) .into_iter() .map(map_jump_hop_gallery_entry), ); entries.extend( wooden_fish_gallery_card_view(ctx) .into_iter() .map(map_wooden_fish_gallery_entry), ); entries.extend( match3d_gallery_view(ctx) .into_iter() .map(map_match3d_gallery_entry), ); entries.extend( square_hole_gallery_view(ctx) .into_iter() .map(map_square_hole_gallery_entry), ); entries.extend( visual_novel_gallery_view(ctx) .into_iter() .map(map_visual_novel_gallery_entry), ); entries.extend( big_fish_gallery_view(ctx) .into_iter() .map(map_big_fish_gallery_entry), ); entries.extend( bark_battle_gallery_view(ctx) .into_iter() .map(map_bark_battle_gallery_entry), ); sort_public_work_gallery_entries(&mut entries); entries } /// 跨玩法公开作品详情摘要读模型。 /// /// `detail_payload_json` 只承载平台详情页展示扩展字段,不承载正式 runtime 配置。 #[spacetimedb::view(accessor = public_work_detail_entry, public)] pub fn public_work_detail_entry(ctx: &AnonymousViewContext) -> Vec { let mut entries = Vec::new(); entries.extend( puzzle_gallery_view(ctx) .into_iter() .map(map_puzzle_detail_entry), ); entries.extend( custom_world_public_profile_snapshots(ctx) .into_iter() .map(map_custom_world_detail_entry), ); entries.extend( jump_hop_gallery_view(ctx) .into_iter() .map(map_jump_hop_detail_entry), ); entries.extend( wooden_fish_gallery_view(ctx) .into_iter() .map(map_wooden_fish_detail_entry), ); entries.extend( match3d_gallery_view(ctx) .into_iter() .map(map_match3d_detail_entry), ); entries.extend( square_hole_gallery_view(ctx) .into_iter() .map(map_square_hole_detail_entry), ); entries.extend( visual_novel_gallery_view(ctx) .into_iter() .map(map_visual_novel_detail_entry), ); entries.extend( big_fish_gallery_view(ctx) .into_iter() .map(map_big_fish_detail_entry), ); entries.extend( bark_battle_gallery_view(ctx) .into_iter() .map(map_bark_battle_detail_entry), ); entries.sort_by(|left, right| { right .sort_time_micros .cmp(&left.sort_time_micros) .then_with(|| left.source_type.cmp(&right.source_type)) .then_with(|| left.profile_id.cmp(&right.profile_id)) }); entries } #[derive(Clone, Debug, PartialEq, Eq, SpacetimeType)] pub struct PublicWorkGalleryEntry { pub source_type: String, pub work_id: String, pub profile_id: String, pub source_session_id: Option, pub public_work_code: String, pub owner_user_id: String, pub author_display_name: String, pub world_name: String, pub subtitle: String, pub summary_text: String, pub cover_image_src: Option, pub cover_asset_id: Option, pub theme_tags: Vec, pub play_count: u32, pub remix_count: u32, pub like_count: u32, pub published_at_micros: Option, pub updated_at_micros: i64, pub sort_time_micros: i64, } #[derive(Clone, Debug, PartialEq, Eq, SpacetimeType)] pub struct PublicWorkDetailEntry { pub source_type: String, pub work_id: String, pub profile_id: String, pub source_session_id: Option, pub public_work_code: String, pub owner_user_id: String, pub author_display_name: String, pub world_name: String, pub subtitle: String, pub summary_text: String, pub cover_image_src: Option, pub cover_asset_id: Option, pub theme_tags: Vec, pub play_count: u32, pub remix_count: u32, pub like_count: u32, pub published_at_micros: Option, pub updated_at_micros: i64, pub sort_time_micros: i64, pub detail_payload_json: String, } fn sort_public_work_gallery_entries(entries: &mut [PublicWorkGalleryEntry]) { entries.sort_by(|left, right| { right .sort_time_micros .cmp(&left.sort_time_micros) .then_with(|| left.source_type.cmp(&right.source_type)) .then_with(|| left.profile_id.cmp(&right.profile_id)) }); } fn gallery_to_detail( entry: PublicWorkGalleryEntry, detail_payload_json: String, ) -> PublicWorkDetailEntry { PublicWorkDetailEntry { source_type: entry.source_type, work_id: entry.work_id, profile_id: entry.profile_id, source_session_id: entry.source_session_id, public_work_code: entry.public_work_code, owner_user_id: entry.owner_user_id, author_display_name: entry.author_display_name, world_name: entry.world_name, subtitle: entry.subtitle, summary_text: entry.summary_text, cover_image_src: entry.cover_image_src, cover_asset_id: entry.cover_asset_id, theme_tags: entry.theme_tags, play_count: entry.play_count, remix_count: entry.remix_count, like_count: entry.like_count, published_at_micros: entry.published_at_micros, updated_at_micros: entry.updated_at_micros, sort_time_micros: entry.sort_time_micros, detail_payload_json, } } fn map_puzzle_gallery_entry(row: PuzzleGalleryCardViewRow) -> PublicWorkGalleryEntry { let world_name = choose_non_empty(&[row.work_title.as_str(), row.level_name.as_str()]); let summary_text = choose_non_empty(&[row.work_description.as_str(), row.summary.as_str()]); let sort_time_micros = row.published_at_micros.unwrap_or(row.updated_at_micros); PublicWorkGalleryEntry { source_type: "puzzle".to_string(), work_id: row.work_id, profile_id: row.profile_id.clone(), source_session_id: row.source_session_id, public_work_code: build_prefixed_public_work_code("PZ", &row.profile_id), owner_user_id: row.owner_user_id, author_display_name: row.author_display_name, world_name, subtitle: "拼图关卡".to_string(), summary_text, cover_image_src: row.cover_image_src, cover_asset_id: row.cover_asset_id, theme_tags: row.theme_tags, play_count: row.play_count, remix_count: row.remix_count, like_count: row.like_count, published_at_micros: row.published_at_micros, updated_at_micros: row.updated_at_micros, sort_time_micros, } } fn map_puzzle_detail_entry(row: PuzzleWorkProfile) -> PublicWorkDetailEntry { let entry = map_puzzle_gallery_entry(PuzzleGalleryCardViewRow { work_id: row.work_id, profile_id: row.profile_id, owner_user_id: row.owner_user_id, source_session_id: row.source_session_id, author_display_name: row.author_display_name, work_title: row.work_title, work_description: row.work_description, level_name: row.level_name, summary: row.summary, theme_tags: row.theme_tags, cover_image_src: row.cover_image_src, cover_asset_id: row.cover_asset_id, publication_status: row.publication_status, updated_at_micros: row.updated_at_micros, published_at_micros: row.published_at_micros, play_count: row.play_count, remix_count: row.remix_count, like_count: row.like_count, point_incentive_total_half_points: row.point_incentive_total_half_points, point_incentive_claimed_points: row.point_incentive_claimed_points, publish_ready: row.publish_ready, generation_status: None, }); let detail_payload_json = json_string(json!({ "sourceType": "puzzle", "levelCount": row.levels.len(), "coverSlides": row.levels.iter().filter_map(|level| { level.cover_image_src.as_ref().map(|image_src| json!({ "id": level.level_id, "imageSrc": image_src, "label": level.level_name, })) }).collect::>(), })); gallery_to_detail(entry, detail_payload_json) } fn map_custom_world_gallery_entry(row: CustomWorldGalleryEntrySnapshot) -> PublicWorkGalleryEntry { PublicWorkGalleryEntry { source_type: "custom-world".to_string(), work_id: format!("custom-world-work-{}", row.profile_id), profile_id: row.profile_id, source_session_id: None, public_work_code: row.public_work_code, owner_user_id: row.owner_user_id, author_display_name: row.author_display_name, world_name: row.world_name, subtitle: row.subtitle, summary_text: row.summary_text, cover_image_src: row.cover_image_src, cover_asset_id: None, theme_tags: vec![format_custom_world_theme_mode(row.theme_mode).to_string()], play_count: row.play_count, remix_count: row.remix_count, like_count: row.like_count, published_at_micros: Some(row.published_at_micros), updated_at_micros: row.updated_at_micros, sort_time_micros: row.published_at_micros, } } fn map_custom_world_detail_entry(row: CustomWorldProfileSnapshot) -> PublicWorkDetailEntry { let public_work_code = row .public_work_code .clone() .unwrap_or_else(|| custom_world::build_custom_world_public_work_code(&row.profile_id)); let published_at_micros = row.published_at_micros.unwrap_or(row.updated_at_micros); let entry = PublicWorkGalleryEntry { source_type: "custom-world".to_string(), work_id: format!("custom-world-work-{}", row.profile_id), profile_id: row.profile_id, source_session_id: row.source_agent_session_id.clone(), public_work_code, owner_user_id: row.owner_user_id, author_display_name: row.author_display_name, world_name: row.world_name, subtitle: row.subtitle, summary_text: row.summary_text, cover_image_src: row.cover_image_src, cover_asset_id: None, theme_tags: vec![format_custom_world_theme_mode(row.theme_mode).to_string()], play_count: row.play_count, remix_count: row.remix_count, like_count: row.like_count, published_at_micros: Some(published_at_micros), updated_at_micros: row.updated_at_micros, sort_time_micros: published_at_micros, }; let detail_payload_json = json_string(json!({ "sourceType": "custom-world", "authorPublicUserCode": row.author_public_user_code, "sourceAgentSessionId": row.source_agent_session_id, "themeMode": format_custom_world_theme_mode(row.theme_mode), "playableNpcCount": row.playable_npc_count, "landmarkCount": row.landmark_count, })); gallery_to_detail(entry, detail_payload_json) } fn map_jump_hop_gallery_entry(row: JumpHopGalleryCardViewRow) -> PublicWorkGalleryEntry { let subtitle = jump_hop_difficulty_label(&row.difficulty).to_string(); let sort_time_micros = row.published_at_micros.unwrap_or(row.updated_at_micros); PublicWorkGalleryEntry { source_type: "jump-hop".to_string(), work_id: row.work_id, profile_id: row.profile_id, source_session_id: None, public_work_code: row.public_work_code, owner_user_id: row.owner_user_id, author_display_name: row.author_display_name, world_name: row.work_title, subtitle, summary_text: row.work_description, cover_image_src: empty_string_to_option(row.cover_image_src), cover_asset_id: None, theme_tags: fallback_tags(row.theme_tags, &["跳一跳"]), play_count: row.play_count, remix_count: 0, like_count: 0, published_at_micros: row.published_at_micros, updated_at_micros: row.updated_at_micros, sort_time_micros, } } fn map_jump_hop_detail_entry(row: JumpHopGalleryViewRow) -> PublicWorkDetailEntry { let entry = PublicWorkGalleryEntry { source_type: "jump-hop".to_string(), work_id: row.work_id, profile_id: row.profile_id.clone(), source_session_id: empty_string_to_option(row.source_session_id.clone()), public_work_code: build_prefixed_public_work_code("JH", &row.profile_id), owner_user_id: row.owner_user_id, author_display_name: row.author_display_name, world_name: row.work_title, subtitle: jump_hop_difficulty_label(&row.difficulty).to_string(), summary_text: row.work_description, cover_image_src: empty_string_to_option(row.cover_image_src), cover_asset_id: None, theme_tags: fallback_tags(row.theme_tags, &["跳一跳"]), play_count: row.play_count, remix_count: 0, like_count: 0, published_at_micros: row.published_at_micros, updated_at_micros: row.updated_at_micros, sort_time_micros: row.published_at_micros.unwrap_or(row.updated_at_micros), }; let detail_payload_json = json_string(json!({ "sourceType": "jump-hop", "difficulty": row.difficulty, "stylePreset": row.style_preset, "tileAssetCount": row.tile_assets.len(), "platformCount": row.path.platforms.len(), "generationStatus": row.generation_status, })); gallery_to_detail(entry, detail_payload_json) } fn map_wooden_fish_gallery_entry(row: WoodenFishGalleryCardViewRow) -> PublicWorkGalleryEntry { let sort_time_micros = row.published_at_micros.unwrap_or(row.updated_at_micros); PublicWorkGalleryEntry { source_type: "wooden-fish".to_string(), work_id: row.work_id, profile_id: row.profile_id, source_session_id: None, public_work_code: row.public_work_code, owner_user_id: row.owner_user_id, author_display_name: row.author_display_name, world_name: row.work_title, subtitle: "敲木鱼".to_string(), summary_text: row.work_description, cover_image_src: empty_string_to_option(row.cover_image_src), cover_asset_id: None, theme_tags: fallback_tags(row.theme_tags, &["敲木鱼"]), play_count: row.play_count, remix_count: 0, like_count: 0, published_at_micros: row.published_at_micros, updated_at_micros: row.updated_at_micros, sort_time_micros, } } fn map_wooden_fish_detail_entry(row: WoodenFishGalleryViewRow) -> PublicWorkDetailEntry { let entry = PublicWorkGalleryEntry { source_type: "wooden-fish".to_string(), work_id: row.work_id, profile_id: row.profile_id, source_session_id: empty_string_to_option(row.source_session_id), public_work_code: row.public_work_code, owner_user_id: row.owner_user_id, author_display_name: row.author_display_name, world_name: row.work_title, subtitle: "敲木鱼".to_string(), summary_text: row.work_description, cover_image_src: empty_string_to_option(row.cover_image_src), cover_asset_id: None, theme_tags: fallback_tags(row.theme_tags, &["敲木鱼"]), play_count: row.play_count, remix_count: 0, like_count: 0, published_at_micros: row.published_at_micros, updated_at_micros: row.updated_at_micros, sort_time_micros: row.published_at_micros.unwrap_or(row.updated_at_micros), }; let detail_payload_json = json_string(json!({ "sourceType": "wooden-fish", "hitObjectPrompt": row.hit_object_prompt, "floatingWords": row.floating_words, "generationStatus": row.generation_status, "hasBackgroundAsset": row.background_asset.is_some(), "hasHitSoundAsset": row.hit_sound_asset.is_some(), })); gallery_to_detail(entry, detail_payload_json) } fn map_match3d_gallery_entry(row: Match3DGalleryViewRow) -> PublicWorkGalleryEntry { let sort_time_micros = row.published_at_micros.unwrap_or(row.updated_at_micros); PublicWorkGalleryEntry { source_type: "match3d".to_string(), work_id: row.profile_id.clone(), profile_id: row.profile_id.clone(), source_session_id: empty_string_to_option(row.source_session_id), public_work_code: build_prefixed_public_work_code("M3", &row.profile_id), owner_user_id: row.owner_user_id, author_display_name: row.author_display_name, world_name: row.game_name, subtitle: "经典消除玩法".to_string(), summary_text: row.summary_text, cover_image_src: empty_string_to_option(row.cover_image_src), cover_asset_id: empty_string_to_option(row.cover_asset_id), theme_tags: fallback_tags(row.tags, &[row.theme_text.as_str(), "抓大鹅"]), play_count: row.play_count, remix_count: 0, like_count: 0, published_at_micros: row.published_at_micros, updated_at_micros: row.updated_at_micros, sort_time_micros, } } fn map_match3d_detail_entry(row: Match3DGalleryViewRow) -> PublicWorkDetailEntry { let detail_payload_json = json_string(json!({ "sourceType": "match3d", "themeText": row.theme_text, "referenceImageSrc": row.reference_image_src, "clearCount": row.clear_count, "difficulty": row.difficulty, "generatedItemAssetsReady": row.generated_item_assets_json.as_ref().is_some_and(|value| !value.trim().is_empty()), })); gallery_to_detail(map_match3d_gallery_entry(row), detail_payload_json) } fn map_square_hole_gallery_entry(row: SquareHoleGalleryViewRow) -> PublicWorkGalleryEntry { let sort_time_micros = row.published_at_micros.unwrap_or(row.updated_at_micros); PublicWorkGalleryEntry { source_type: "square-hole".to_string(), work_id: row.work_id, profile_id: row.profile_id.clone(), source_session_id: empty_string_to_option(row.source_session_id), public_work_code: build_prefixed_public_work_code("SH", &row.profile_id), owner_user_id: row.owner_user_id, author_display_name: row.author_display_name, world_name: row.game_name, subtitle: choose_non_empty(&[row.twist_rule.as_str(), "反直觉形状分拣"]), summary_text: row.summary_text, cover_image_src: empty_string_to_option(row.cover_image_src), cover_asset_id: None, theme_tags: fallback_tags(row.tags, &[row.theme_text.as_str(), "方洞挑战"]), play_count: row.play_count, remix_count: 0, like_count: 0, published_at_micros: row.published_at_micros, updated_at_micros: row.updated_at_micros, sort_time_micros, } } fn map_square_hole_detail_entry(row: SquareHoleGalleryViewRow) -> PublicWorkDetailEntry { let detail_payload_json = json_string(json!({ "sourceType": "square-hole", "themeText": row.theme_text, "twistRule": row.twist_rule, "backgroundPrompt": row.background_prompt, "backgroundImageSrc": empty_string_to_option(row.background_image_src.clone()), "shapeCount": row.shape_count, "difficulty": row.difficulty, "shapeOptionCount": row.shape_options.len(), "holeOptionCount": row.hole_options.len(), })); gallery_to_detail(map_square_hole_gallery_entry(row), detail_payload_json) } fn map_visual_novel_gallery_entry(row: VisualNovelGalleryViewRow) -> PublicWorkGalleryEntry { let sort_time_micros = row.published_at_micros.unwrap_or(row.updated_at_micros); PublicWorkGalleryEntry { source_type: "visual-novel".to_string(), work_id: row.work_id, profile_id: row.profile_id.clone(), source_session_id: row.source_session_id, public_work_code: build_prefixed_public_work_code("VN", &row.profile_id), owner_user_id: row.owner_user_id, author_display_name: row.author_display_name, world_name: row.work_title, subtitle: "视觉小说模板".to_string(), summary_text: row.work_description, cover_image_src: row.cover_image_src, cover_asset_id: None, theme_tags: fallback_tags(row.tags, &["视觉小说"]), play_count: row.play_count, remix_count: 0, like_count: 0, published_at_micros: row.published_at_micros, updated_at_micros: row.updated_at_micros, sort_time_micros, } } fn map_visual_novel_detail_entry(row: VisualNovelGalleryViewRow) -> PublicWorkDetailEntry { let detail_payload_json = json_string(json!({ "sourceType": "visual-novel", "sourceAssetIds": row.source_asset_ids, "createdAtMicros": row.created_at_micros, })); gallery_to_detail(map_visual_novel_gallery_entry(row), detail_payload_json) } fn map_big_fish_gallery_entry(row: BigFishWorkSummarySnapshot) -> PublicWorkGalleryEntry { let sort_time_micros = row.published_at_micros.unwrap_or(row.updated_at_micros); PublicWorkGalleryEntry { source_type: "big-fish".to_string(), work_id: row.work_id, profile_id: row.source_session_id.clone(), source_session_id: Some(row.source_session_id.clone()), public_work_code: build_prefixed_public_work_code("BF", &row.source_session_id), owner_user_id: row.owner_user_id, author_display_name: "玩家".to_string(), world_name: row.title, subtitle: choose_non_empty(&[row.subtitle.as_str(), "大鱼吃小鱼"]), summary_text: row.summary, cover_image_src: row.cover_image_src, cover_asset_id: None, theme_tags: vec!["大鱼".to_string(), format!("{}级", row.level_count)], play_count: row.play_count, remix_count: row.remix_count, like_count: row.like_count, published_at_micros: row.published_at_micros, updated_at_micros: row.updated_at_micros, sort_time_micros, } } fn map_big_fish_detail_entry(row: BigFishWorkSummarySnapshot) -> PublicWorkDetailEntry { let detail_payload_json = json_string(json!({ "sourceType": "big-fish", "status": row.status, "publishReady": row.publish_ready, "levelCount": row.level_count, "levelMainImageReadyCount": row.level_main_image_ready_count, "levelMotionReadyCount": row.level_motion_ready_count, "backgroundReady": row.background_ready, })); gallery_to_detail(map_big_fish_gallery_entry(row), detail_payload_json) } fn map_bark_battle_gallery_entry(row: BarkBattleGalleryViewRow) -> PublicWorkGalleryEntry { let cover_image_src = row .ui_background_image_src .clone() .or_else(|| row.player_character_image_src.clone()) .or_else(|| row.opponent_character_image_src.clone()) .or_else(|| Some("/creation-type-references/bark-battle.webp".to_string())); PublicWorkGalleryEntry { source_type: "bark-battle".to_string(), work_id: row.work_id.clone(), profile_id: row.work_id.clone(), source_session_id: row.source_draft_id, public_work_code: build_bark_battle_public_work_code(&row.work_id), owner_user_id: row.owner_user_id, author_display_name: "玩家".to_string(), world_name: choose_non_empty(&[row.title.as_str(), "汪汪声浪大作战"]), subtitle: format!( "汪汪声浪 · {}", bark_battle_difficulty_label(&row.difficulty_preset) ), summary_text: choose_non_empty(&[ row.description.as_str(), row.theme_description.as_str(), "用声音能量挑战对手。", ]), cover_image_src, cover_asset_id: None, theme_tags: vec![ "汪汪声浪".to_string(), bark_battle_difficulty_label(&row.difficulty_preset).to_string(), ], play_count: saturating_u64_to_u32(row.play_count), remix_count: 0, like_count: 0, published_at_micros: Some(row.published_at_micros), updated_at_micros: row.updated_at_micros, sort_time_micros: row.published_at_micros, } } fn map_bark_battle_detail_entry(row: BarkBattleGalleryViewRow) -> PublicWorkDetailEntry { let detail_payload_json = json_string(json!({ "sourceType": "bark-battle", "difficultyPreset": row.difficulty_preset, "themeDescription": row.theme_description, "playerImageDescription": row.player_image_description, "opponentImageDescription": row.opponent_image_description, "onomatopoeia": row.onomatopoeia, "playerCharacterImageSrc": row.player_character_image_src, "opponentCharacterImageSrc": row.opponent_character_image_src, "uiBackgroundImageSrc": row.ui_background_image_src, "finishCount": row.finish_count, })); gallery_to_detail(map_bark_battle_gallery_entry(row), detail_payload_json) } fn build_prefixed_public_work_code(prefix: &str, value: &str) -> String { let normalized = normalize_public_code_text(value); let fallback = if normalized.is_empty() { "00000000".to_string() } else { normalized }; let suffix = last_eight_padded(&fallback); format!("{prefix}-{suffix}") } fn build_bark_battle_public_work_code(work_id: &str) -> String { let normalized = normalize_public_code_text(work_id); let without_prefix = normalized .strip_prefix("BB") .map(ToString::to_string) .unwrap_or_else(|| normalized.clone()); let fallback = if without_prefix.is_empty() { if normalized.is_empty() { "00000000".to_string() } else { normalized } } else { without_prefix }; format!("BB-{}", last_eight_padded(&fallback)) } fn normalize_public_code_text(value: &str) -> String { value .trim() .chars() .filter(|character| character.is_ascii_alphanumeric()) .flat_map(char::to_uppercase) .collect() } fn last_eight_padded(value: &str) -> String { let suffix = value .chars() .rev() .take(8) .collect::>() .into_iter() .rev() .collect::(); format!("{suffix:0>8}") } fn choose_non_empty(values: &[&str]) -> String { values .iter() .map(|value| value.trim()) .find(|value| !value.is_empty()) .unwrap_or_default() .to_string() } fn empty_string_to_option(value: String) -> Option { let value = value.trim().to_string(); (!value.is_empty()).then_some(value) } fn fallback_tags(values: Vec, fallback: &[&str]) -> Vec { let normalized = values .into_iter() .map(|value| value.trim().to_string()) .filter(|value| !value.is_empty()) .collect::>(); if normalized.is_empty() { fallback .iter() .map(|value| value.trim().to_string()) .filter(|value| !value.is_empty()) .collect() } else { normalized } } fn jump_hop_difficulty_label(value: &str) -> &'static str { match value.trim() { "easy" => "轻松节奏", "advanced" => "进阶跳台", "challenge" => "极限路线", _ => "标准路线", } } fn bark_battle_difficulty_label(value: &str) -> &'static str { match value.trim() { "easy" => "轻松", "hard" => "高能", _ => "普通", } } fn format_custom_world_theme_mode(value: CustomWorldThemeMode) -> &'static str { match value { CustomWorldThemeMode::Martial => "martial", CustomWorldThemeMode::Arcane => "arcane", CustomWorldThemeMode::Machina => "machina", CustomWorldThemeMode::Tide => "tide", CustomWorldThemeMode::Rift => "rift", CustomWorldThemeMode::Mythic => "mythic", } } fn saturating_u64_to_u32(value: u64) -> u32 { value.min(u64::from(u32::MAX)) as u32 } fn json_string(value: JsonValue) -> String { serde_json::to_string(&value).unwrap_or_else(|_| "{}".to_string()) }