use super::*; pub use shared_contracts::jump_hop::{ JumpHopActionRequest, JumpHopActionResponse, JumpHopActionType, JumpHopCharacterAsset, JumpHopDefaultCharacter, JumpHopDifficulty, JumpHopDraftResponse, JumpHopGalleryCardResponse, JumpHopGalleryDetailResponse, JumpHopGalleryResponse, JumpHopGenerationStatus, JumpHopJumpRequest, JumpHopJumpResponse, JumpHopJumpResult, JumpHopLastJump, JumpHopLeaderboardEntry, JumpHopLeaderboardResponse, JumpHopPath, JumpHopPlatform, JumpHopRestartRunRequest, JumpHopRunResponse, JumpHopRunStatus, JumpHopRuntimeRunSnapshotResponse, JumpHopScoring, JumpHopSessionResponse, JumpHopSessionSnapshotResponse, JumpHopStartRunRequest, JumpHopStylePreset, JumpHopTileAsset, JumpHopTileType, JumpHopWorkDetailResponse, JumpHopWorkMutationResponse, JumpHopWorkProfileResponse, JumpHopWorkSummaryResponse, JumpHopWorksResponse, JumpHopWorkspaceCreateRequest, }; pub(crate) fn map_jump_hop_agent_session_procedure_result( result: JumpHopAgentSessionProcedureResult, ) -> Result { if !result.ok { return Err(SpacetimeClientError::procedure_failed(result.error_message)); } let session = result .session .ok_or_else(|| SpacetimeClientError::missing_snapshot("jump hop agent session 快照"))?; Ok(map_jump_hop_session_snapshot(session)) } pub(crate) fn map_jump_hop_work_procedure_result( result: JumpHopWorkProcedureResult, ) -> Result { if !result.ok { return Err(SpacetimeClientError::procedure_failed(result.error_message)); } let work = result .work .ok_or_else(|| SpacetimeClientError::missing_snapshot("jump hop work 快照"))?; map_jump_hop_work_snapshot(work) } pub(crate) fn map_jump_hop_works_procedure_result( result: JumpHopWorksProcedureResult, ) -> Result, SpacetimeClientError> { if !result.ok { return Err(SpacetimeClientError::procedure_failed(result.error_message)); } result .items .into_iter() .map(map_jump_hop_work_snapshot) .collect() } pub(crate) fn map_jump_hop_run_procedure_result( result: JumpHopRunProcedureResult, ) -> Result { if !result.ok { return Err(SpacetimeClientError::procedure_failed(result.error_message)); } let run = result .run .ok_or_else(|| SpacetimeClientError::missing_snapshot("jump hop run 快照"))?; Ok(map_jump_hop_run_snapshot(run)) } pub(crate) fn map_jump_hop_leaderboard_procedure_result( result: JumpHopLeaderboardProcedureResult, ) -> Result { if !result.ok { return Err(SpacetimeClientError::procedure_failed(result.error_message)); } Ok(JumpHopLeaderboardResponse { profile_id: result.profile_id, items: result .items .into_iter() .map(map_jump_hop_leaderboard_entry_snapshot) .collect(), viewer_best: result .viewer_best .map(map_jump_hop_leaderboard_entry_snapshot), }) } pub(crate) fn map_jump_hop_gallery_card_view_row( row: JumpHopGalleryCardViewRow, ) -> JumpHopGalleryCardResponse { let theme_text = if row.theme_text.trim().is_empty() { row.work_title.clone() } else { row.theme_text.clone() }; JumpHopGalleryCardResponse { public_work_code: row.public_work_code, work_id: row.work_id, profile_id: row.profile_id, owner_user_id: row.owner_user_id, author_display_name: row.author_display_name, theme_text, work_title: row.work_title, work_description: row.work_description, cover_image_src: empty_string_to_none(row.cover_image_src), theme_tags: row.theme_tags, difficulty: parse_difficulty(&row.difficulty), style_preset: parse_style_preset(&row.style_preset), publication_status: normalize_publication_status(&row.publication_status).to_string(), play_count: row.play_count, updated_at: format_timestamp_micros(row.updated_at_micros), published_at: row.published_at_micros.map(format_timestamp_micros), generation_status: parse_generation_status(&row.generation_status), } } fn map_jump_hop_session_snapshot( snapshot: JumpHopAgentSessionSnapshot, ) -> JumpHopSessionSnapshotResponse { JumpHopSessionSnapshotResponse { session_id: snapshot.session_id, owner_user_id: snapshot.owner_user_id, status: snapshot .draft .as_ref() .map(|draft| parse_generation_status(&draft.generation_status)) .unwrap_or(JumpHopGenerationStatus::Draft), draft: snapshot.draft.map(map_jump_hop_draft_snapshot), created_at: format_timestamp_micros(snapshot.created_at_micros), updated_at: format_timestamp_micros(snapshot.updated_at_micros), } } fn map_jump_hop_work_snapshot( snapshot: JumpHopWorkSnapshot, ) -> Result { let theme_text = if snapshot.theme_text.trim().is_empty() { snapshot.work_title.clone() } else { snapshot.theme_text.clone() }; let draft = JumpHopDraftResponse { template_id: "jump-hop".to_string(), template_name: "跳一跳".to_string(), profile_id: Some(snapshot.profile_id.clone()), theme_text: theme_text.clone(), work_title: snapshot.work_title.clone(), work_description: snapshot.work_description.clone(), theme_tags: snapshot.theme_tags.clone(), difficulty: parse_difficulty(&snapshot.difficulty), style_preset: parse_style_preset(&snapshot.style_preset), default_character: Some(default_jump_hop_character()), character_prompt: snapshot.character_prompt.clone(), tile_prompt: snapshot.tile_prompt.clone(), end_mood_prompt: snapshot.end_mood_prompt.clone(), character_asset: snapshot.character_asset.clone().map(map_character_asset), tile_atlas_asset: snapshot.tile_atlas_asset.clone().map(map_character_asset), tile_assets: snapshot .tile_assets .clone() .into_iter() .map(map_tile_asset) .collect(), path: Some(map_jump_hop_path(snapshot.path.clone())), cover_composite: snapshot.cover_composite.clone(), generation_status: parse_generation_status(&snapshot.generation_status), }; let character_asset = draft .character_asset .clone() .ok_or_else(|| SpacetimeClientError::missing_snapshot("jump hop character asset"))?; let tile_atlas_asset = draft .tile_atlas_asset .clone() .ok_or_else(|| SpacetimeClientError::missing_snapshot("jump hop tile atlas asset"))?; Ok(JumpHopWorkProfileResponse { summary: JumpHopWorkSummaryResponse { runtime_kind: "jump-hop".to_string(), work_id: snapshot.work_id, profile_id: snapshot.profile_id, owner_user_id: snapshot.owner_user_id, source_session_id: empty_string_to_none(snapshot.source_session_id), theme_text, work_title: snapshot.work_title, work_description: snapshot.work_description, theme_tags: snapshot.theme_tags, difficulty: parse_difficulty(&snapshot.difficulty), style_preset: parse_style_preset(&snapshot.style_preset), cover_image_src: empty_string_to_none(snapshot.cover_image_src), publication_status: normalize_publication_status(&snapshot.publication_status) .to_string(), play_count: snapshot.play_count, updated_at: format_timestamp_micros(snapshot.updated_at_micros), published_at: snapshot.published_at_micros.map(format_timestamp_micros), publish_ready: snapshot.publish_ready, generation_status: parse_generation_status(&snapshot.generation_status), }, draft, path: map_jump_hop_path(snapshot.path), default_character: Some(default_jump_hop_character()), character_asset, tile_atlas_asset, tile_assets: snapshot .tile_assets .into_iter() .map(map_tile_asset) .collect(), }) } fn map_jump_hop_draft_snapshot(snapshot: JumpHopDraftSnapshot) -> JumpHopDraftResponse { let theme_text = if snapshot.theme_text.trim().is_empty() { snapshot.work_title.clone() } else { snapshot.theme_text.clone() }; JumpHopDraftResponse { template_id: snapshot.template_id, template_name: snapshot.template_name, profile_id: snapshot.profile_id, theme_text, work_title: snapshot.work_title, work_description: snapshot.work_description, theme_tags: snapshot.theme_tags, difficulty: parse_difficulty(&snapshot.difficulty), style_preset: parse_style_preset(&snapshot.style_preset), default_character: Some(default_jump_hop_character()), character_prompt: snapshot.character_prompt, tile_prompt: snapshot.tile_prompt, end_mood_prompt: snapshot.end_mood_prompt, character_asset: snapshot.character_asset.map(map_character_asset), tile_atlas_asset: snapshot.tile_atlas_asset.map(map_character_asset), tile_assets: snapshot .tile_assets .into_iter() .map(map_tile_asset) .collect(), path: snapshot.path.map(map_jump_hop_path), cover_composite: snapshot.cover_composite, generation_status: parse_generation_status(&snapshot.generation_status), } } fn map_character_asset(snapshot: JumpHopCharacterAssetSnapshot) -> JumpHopCharacterAsset { JumpHopCharacterAsset { asset_id: snapshot.asset_id, image_src: snapshot.image_src, image_object_key: snapshot.image_object_key, asset_object_id: snapshot.asset_object_id, generation_provider: snapshot.generation_provider, prompt: snapshot.prompt, width: snapshot.width, height: snapshot.height, } } fn map_tile_asset(snapshot: JumpHopTileAssetSnapshot) -> JumpHopTileAsset { JumpHopTileAsset { tile_type: parse_tile_type(&snapshot.tile_type), tile_id: snapshot.tile_id, image_src: snapshot.image_src, image_object_key: snapshot.image_object_key, asset_object_id: snapshot.asset_object_id, source_atlas_cell: snapshot.source_atlas_cell, atlas_row: snapshot.atlas_row, atlas_col: snapshot.atlas_col, visual_width: snapshot.visual_width, visual_height: snapshot.visual_height, top_surface_radius: snapshot.top_surface_radius, landing_radius: snapshot.landing_radius, } } fn map_jump_hop_path(snapshot: crate::module_bindings::JumpHopPath) -> JumpHopPath { JumpHopPath { seed: snapshot.seed, difficulty: parse_domain_difficulty(snapshot.difficulty), platforms: snapshot .platforms .into_iter() .map(|platform| JumpHopPlatform { platform_id: platform.platform_id, tile_type: parse_domain_tile_type(platform.tile_type), x: platform.x, y: platform.y, width: platform.width, height: platform.height, landing_radius: platform.landing_radius, perfect_radius: platform.perfect_radius, score_value: platform.score_value, }) .collect(), finish_index: snapshot.finish_index, camera_preset: snapshot.camera_preset, scoring: JumpHopScoring { charge_to_distance_ratio: snapshot.scoring.charge_to_distance_ratio, max_charge_ms: snapshot.scoring.max_charge_ms, hit_bonus: snapshot.scoring.hit_bonus, perfect_bonus: snapshot.scoring.perfect_bonus, }, } } fn map_jump_hop_run_snapshot(snapshot: JumpHopRunSnapshot) -> JumpHopRuntimeRunSnapshotResponse { JumpHopRuntimeRunSnapshotResponse { run_id: snapshot.run_id, profile_id: snapshot.profile_id, owner_user_id: snapshot.owner_user_id, status: match snapshot.status { crate::module_bindings::JumpHopRunStatus::Failed => JumpHopRunStatus::Failed, crate::module_bindings::JumpHopRunStatus::Cleared => JumpHopRunStatus::Cleared, crate::module_bindings::JumpHopRunStatus::Playing => JumpHopRunStatus::Playing, }, current_platform_index: snapshot.current_platform_index, successful_jump_count: snapshot.current_platform_index, duration_ms: jump_hop_duration_ms(snapshot.started_at_ms, snapshot.finished_at_ms), score: snapshot.score, combo: snapshot.combo, path: map_jump_hop_path(snapshot.path), last_jump: snapshot.last_jump.map(|jump| JumpHopLastJump { charge_ms: jump.charge_ms, jump_distance: jump.jump_distance, target_platform_index: jump.target_platform_index, landed_x: jump.landed_x, landed_y: jump.landed_y, result: match jump.result { crate::module_bindings::JumpHopJumpResultKind::Miss => JumpHopJumpResult::Miss, crate::module_bindings::JumpHopJumpResultKind::Hit => JumpHopJumpResult::Hit, crate::module_bindings::JumpHopJumpResultKind::Finish => JumpHopJumpResult::Finish, crate::module_bindings::JumpHopJumpResultKind::Perfect => { JumpHopJumpResult::Perfect } }, }), started_at_ms: snapshot.started_at_ms, finished_at_ms: snapshot.finished_at_ms, } } fn map_jump_hop_leaderboard_entry_snapshot( snapshot: JumpHopLeaderboardEntrySnapshot, ) -> JumpHopLeaderboardEntry { JumpHopLeaderboardEntry { rank: snapshot.rank, player_id: snapshot.player_id, successful_jump_count: snapshot.successful_jump_count, duration_ms: snapshot.duration_ms, updated_at: format_timestamp_micros(snapshot.updated_at_micros), } } fn default_jump_hop_character() -> JumpHopDefaultCharacter { JumpHopDefaultCharacter { character_id: "jump-hop-default-runner".to_string(), display_name: "默认角色".to_string(), model_kind: "builtin-three".to_string(), body_color: "#f59e0b".to_string(), accent_color: "#2563eb".to_string(), } } fn jump_hop_duration_ms(started_at_ms: u64, finished_at_ms: Option) -> u64 { finished_at_ms .unwrap_or(started_at_ms) .saturating_sub(started_at_ms) } fn parse_difficulty(value: &str) -> JumpHopDifficulty { match value { "easy" => JumpHopDifficulty::Easy, "advanced" => JumpHopDifficulty::Advanced, "challenge" => JumpHopDifficulty::Challenge, _ => JumpHopDifficulty::Standard, } } fn parse_domain_difficulty(value: crate::module_bindings::JumpHopDifficulty) -> JumpHopDifficulty { match value { crate::module_bindings::JumpHopDifficulty::Easy => JumpHopDifficulty::Easy, crate::module_bindings::JumpHopDifficulty::Advanced => JumpHopDifficulty::Advanced, crate::module_bindings::JumpHopDifficulty::Challenge => JumpHopDifficulty::Challenge, crate::module_bindings::JumpHopDifficulty::Standard => JumpHopDifficulty::Standard, } } fn parse_style_preset(value: &str) -> JumpHopStylePreset { match value { "paper-toy" => JumpHopStylePreset::PaperToy, "neon-glass" => JumpHopStylePreset::NeonGlass, "forest-stone" => JumpHopStylePreset::ForestStone, "future-metal" => JumpHopStylePreset::FutureMetal, "custom" => JumpHopStylePreset::Custom, _ => JumpHopStylePreset::MinimalBlocks, } } fn parse_tile_type(value: &str) -> JumpHopTileType { match value { "start" => JumpHopTileType::Start, "target" => JumpHopTileType::Target, "finish" => JumpHopTileType::Finish, "bonus" => JumpHopTileType::Bonus, "accent" => JumpHopTileType::Accent, _ => JumpHopTileType::Normal, } } fn parse_domain_tile_type(value: crate::module_bindings::JumpHopTileType) -> JumpHopTileType { match value { crate::module_bindings::JumpHopTileType::Start => JumpHopTileType::Start, crate::module_bindings::JumpHopTileType::Target => JumpHopTileType::Target, crate::module_bindings::JumpHopTileType::Finish => JumpHopTileType::Finish, crate::module_bindings::JumpHopTileType::Bonus => JumpHopTileType::Bonus, crate::module_bindings::JumpHopTileType::Accent => JumpHopTileType::Accent, crate::module_bindings::JumpHopTileType::Normal => JumpHopTileType::Normal, } } fn parse_generation_status(value: &str) -> JumpHopGenerationStatus { match value { "generating" => JumpHopGenerationStatus::Generating, "ready" => JumpHopGenerationStatus::Ready, "failed" => JumpHopGenerationStatus::Failed, _ => JumpHopGenerationStatus::Draft, } } fn normalize_publication_status(value: &str) -> &str { match value { "Published" | "published" => "published", _ => "draft", } }