964 lines
38 KiB
Rust
964 lines
38 KiB
Rust
use serde::{Deserialize, Serialize};
|
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
|
#[serde(rename_all = "snake_case")]
|
|
pub enum BarkBattleDifficultyPreset {
|
|
Easy,
|
|
Normal,
|
|
Hard,
|
|
}
|
|
|
|
impl Default for BarkBattleDifficultyPreset {
|
|
fn default() -> Self {
|
|
Self::Normal
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
|
#[serde(rename_all = "snake_case")]
|
|
pub enum BarkBattleServerResult {
|
|
PlayerWin,
|
|
OpponentWin,
|
|
Draw,
|
|
}
|
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
|
#[serde(rename_all = "snake_case")]
|
|
pub enum BarkBattleFinishStatus {
|
|
Accepted,
|
|
AcceptedWithFlags,
|
|
Rejected,
|
|
}
|
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
|
|
#[serde(rename_all = "kebab-case")]
|
|
pub enum BarkBattleAssetSlot {
|
|
PlayerCharacter,
|
|
OpponentCharacter,
|
|
UiBackground,
|
|
}
|
|
|
|
#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct BarkBattleReplacementConfig {
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub player_character_image_src: Option<String>,
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub opponent_character_image_src: Option<String>,
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub ui_background_image_src: Option<String>,
|
|
}
|
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct BarkBattleConfigEditorPayload {
|
|
pub title: String,
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub description: Option<String>,
|
|
pub theme_description: String,
|
|
pub player_image_description: String,
|
|
pub opponent_image_description: String,
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub onomatopoeia: Option<Vec<String>>,
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub player_character_image_src: Option<String>,
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub opponent_character_image_src: Option<String>,
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub ui_background_image_src: Option<String>,
|
|
#[serde(default)]
|
|
pub difficulty_preset: BarkBattleDifficultyPreset,
|
|
}
|
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct BarkBattleDraftCreateRequest {
|
|
pub title: String,
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub description: Option<String>,
|
|
pub theme_description: String,
|
|
pub player_image_description: String,
|
|
pub opponent_image_description: String,
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub onomatopoeia: Option<Vec<String>>,
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub player_character_image_src: Option<String>,
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub opponent_character_image_src: Option<String>,
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub ui_background_image_src: Option<String>,
|
|
#[serde(default)]
|
|
pub difficulty_preset: BarkBattleDifficultyPreset,
|
|
}
|
|
|
|
impl From<BarkBattleDraftCreateRequest> for BarkBattleConfigEditorPayload {
|
|
fn from(value: BarkBattleDraftCreateRequest) -> Self {
|
|
Self {
|
|
title: value.title,
|
|
description: value.description,
|
|
theme_description: value.theme_description,
|
|
player_image_description: value.player_image_description,
|
|
opponent_image_description: value.opponent_image_description,
|
|
onomatopoeia: value.onomatopoeia,
|
|
player_character_image_src: value.player_character_image_src,
|
|
opponent_character_image_src: value.opponent_character_image_src,
|
|
ui_background_image_src: value.ui_background_image_src,
|
|
difficulty_preset: value.difficulty_preset,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct BarkBattleDraftConfigUpdateRequest {
|
|
pub draft_id: String,
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub work_id: Option<String>,
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub config_version: Option<u32>,
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub ruleset_version: Option<String>,
|
|
pub title: String,
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub description: Option<String>,
|
|
pub theme_description: String,
|
|
pub player_image_description: String,
|
|
pub opponent_image_description: String,
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub onomatopoeia: Option<Vec<String>>,
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub player_character_image_src: Option<String>,
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub opponent_character_image_src: Option<String>,
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub ui_background_image_src: Option<String>,
|
|
#[serde(default)]
|
|
pub difficulty_preset: BarkBattleDifficultyPreset,
|
|
}
|
|
|
|
impl From<BarkBattleDraftConfigUpdateRequest> for BarkBattleConfigEditorPayload {
|
|
fn from(value: BarkBattleDraftConfigUpdateRequest) -> Self {
|
|
Self {
|
|
title: value.title,
|
|
description: value.description,
|
|
theme_description: value.theme_description,
|
|
player_image_description: value.player_image_description,
|
|
opponent_image_description: value.opponent_image_description,
|
|
onomatopoeia: value.onomatopoeia,
|
|
player_character_image_src: value.player_character_image_src,
|
|
opponent_character_image_src: value.opponent_character_image_src,
|
|
ui_background_image_src: value.ui_background_image_src,
|
|
difficulty_preset: value.difficulty_preset,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct BarkBattleWorkPublishRequest {
|
|
pub draft_id: String,
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub work_id: Option<String>,
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub published_snapshot: Option<BarkBattleConfigEditorPayload>,
|
|
}
|
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct BarkBattleImageAssetGenerateRequest {
|
|
pub slot: BarkBattleAssetSlot,
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub draft_id: Option<String>,
|
|
pub config: BarkBattleConfigEditorPayload,
|
|
}
|
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct BarkBattleGeneratedImageAsset {
|
|
pub image_src: String,
|
|
pub asset_id: String,
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub source_type: Option<String>,
|
|
pub model: String,
|
|
pub size: String,
|
|
pub task_id: String,
|
|
pub prompt: String,
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub actual_prompt: Option<String>,
|
|
}
|
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct BarkBattleDraftConfig {
|
|
pub draft_id: String,
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub work_id: Option<String>,
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub config_version: Option<u32>,
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub ruleset_version: Option<String>,
|
|
pub title: String,
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub description: Option<String>,
|
|
pub theme_description: String,
|
|
pub player_image_description: String,
|
|
pub opponent_image_description: String,
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub onomatopoeia: Option<Vec<String>>,
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub player_character_image_src: Option<String>,
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub opponent_character_image_src: Option<String>,
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub ui_background_image_src: Option<String>,
|
|
#[serde(default)]
|
|
pub difficulty_preset: BarkBattleDifficultyPreset,
|
|
pub updated_at: String,
|
|
}
|
|
|
|
impl Default for BarkBattleDraftConfig {
|
|
fn default() -> Self {
|
|
Self {
|
|
draft_id: String::new(),
|
|
work_id: None,
|
|
config_version: None,
|
|
ruleset_version: None,
|
|
title: String::new(),
|
|
description: None,
|
|
theme_description: String::new(),
|
|
player_image_description: String::new(),
|
|
opponent_image_description: String::new(),
|
|
onomatopoeia: None,
|
|
player_character_image_src: None,
|
|
opponent_character_image_src: None,
|
|
ui_background_image_src: None,
|
|
difficulty_preset: BarkBattleDifficultyPreset::Normal,
|
|
updated_at: String::new(),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct BarkBattlePublishedConfig {
|
|
pub work_id: String,
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub draft_id: Option<String>,
|
|
pub config_version: u32,
|
|
pub ruleset_version: String,
|
|
pub play_type_id: String,
|
|
pub title: String,
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub description: Option<String>,
|
|
pub theme_description: String,
|
|
pub player_image_description: String,
|
|
pub opponent_image_description: String,
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub onomatopoeia: Option<Vec<String>>,
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub player_character_image_src: Option<String>,
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub opponent_character_image_src: Option<String>,
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub ui_background_image_src: Option<String>,
|
|
pub difficulty_preset: BarkBattleDifficultyPreset,
|
|
pub updated_at: String,
|
|
pub published_at: String,
|
|
}
|
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct BarkBattleRuntimeConfig {
|
|
pub work_id: String,
|
|
pub config_version: u32,
|
|
pub ruleset_version: String,
|
|
pub play_type_id: String,
|
|
pub duration_ms: u64,
|
|
pub energy_min: f32,
|
|
pub energy_max: f32,
|
|
pub draw_threshold: f32,
|
|
pub min_bark_gap_ms: u64,
|
|
pub difficulty_preset: BarkBattleDifficultyPreset,
|
|
pub theme_description: String,
|
|
pub player_image_description: String,
|
|
pub opponent_image_description: String,
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub onomatopoeia: Option<Vec<String>>,
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub player_character_image_src: Option<String>,
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub opponent_character_image_src: Option<String>,
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub ui_background_image_src: Option<String>,
|
|
pub updated_at: String,
|
|
}
|
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct BarkBattleWorkSummary {
|
|
pub work_id: String,
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub draft_id: Option<String>,
|
|
pub owner_user_id: String,
|
|
pub author_display_name: String,
|
|
pub title: String,
|
|
pub summary: String,
|
|
pub theme_description: String,
|
|
pub player_image_description: String,
|
|
pub opponent_image_description: String,
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub onomatopoeia: Option<Vec<String>>,
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub player_character_image_src: Option<String>,
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub opponent_character_image_src: Option<String>,
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub ui_background_image_src: Option<String>,
|
|
pub difficulty_preset: BarkBattleDifficultyPreset,
|
|
pub status: String,
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub generation_status: Option<String>,
|
|
pub publish_ready: bool,
|
|
pub play_count: u64,
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub finish_count: Option<u64>,
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub win_count: Option<u64>,
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub draw_count: Option<u64>,
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub loss_count: Option<u64>,
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub recent_play_count_7d: Option<u64>,
|
|
pub updated_at: String,
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub published_at: Option<String>,
|
|
}
|
|
|
|
#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct BarkBattleWorksResponse {
|
|
#[serde(default)]
|
|
pub items: Vec<BarkBattleWorkSummary>,
|
|
}
|
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct BarkBattleWorkDetailResponse {
|
|
pub item: BarkBattleWorkSummary,
|
|
}
|
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct BarkBattleRunStartRequest {
|
|
pub work_id: String,
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub config_version: Option<u32>,
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub source_route: Option<String>,
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub client_runtime_version: Option<String>,
|
|
}
|
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct BarkBattleRunStartResponse {
|
|
pub run_id: String,
|
|
pub run_token: String,
|
|
pub work_id: String,
|
|
pub config_version: u32,
|
|
pub ruleset_version: String,
|
|
pub difficulty_preset: BarkBattleDifficultyPreset,
|
|
pub runtime_config: BarkBattleRuntimeConfig,
|
|
pub server_started_at: String,
|
|
pub expires_at: String,
|
|
}
|
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct BarkBattleDerivedMetrics {
|
|
pub trigger_count: u32,
|
|
pub max_volume: f32,
|
|
pub average_volume: f32,
|
|
pub final_energy: f32,
|
|
pub combo_max: u32,
|
|
}
|
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct BarkBattleRunFinishRequest {
|
|
pub work_id: String,
|
|
pub run_id: String,
|
|
pub run_token: String,
|
|
pub config_version: u32,
|
|
pub ruleset_version: String,
|
|
pub difficulty_preset: BarkBattleDifficultyPreset,
|
|
pub client_started_at: String,
|
|
pub client_finished_at: String,
|
|
pub duration_ms: u64,
|
|
pub derived_metrics: BarkBattleDerivedMetrics,
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub client_result: Option<BarkBattleServerResult>,
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub sample_digest: Option<String>,
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub client_runtime_version: Option<String>,
|
|
}
|
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct BarkBattleScoreSummary {
|
|
pub duration_ms: u64,
|
|
pub trigger_count: u32,
|
|
pub max_volume: f32,
|
|
pub average_volume: f32,
|
|
pub final_energy: f32,
|
|
pub combo_max: u32,
|
|
}
|
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct BarkBattleRunFinishResponse {
|
|
pub status: BarkBattleFinishStatus,
|
|
pub run_id: String,
|
|
pub work_id: String,
|
|
pub config_version: u32,
|
|
pub ruleset_version: String,
|
|
pub difficulty_preset: BarkBattleDifficultyPreset,
|
|
pub server_result: BarkBattleServerResult,
|
|
pub score_summary: BarkBattleScoreSummary,
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub leaderboard_score: Option<u64>,
|
|
#[serde(default)]
|
|
pub anti_cheat_flags: Vec<String>,
|
|
pub updated_at: String,
|
|
}
|
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct BarkBattleLeaderboardEntry {
|
|
pub rank: u32,
|
|
pub run_id: String,
|
|
pub work_id: String,
|
|
pub config_version: u32,
|
|
pub ruleset_version: String,
|
|
pub difficulty_preset: BarkBattleDifficultyPreset,
|
|
pub display_name: String,
|
|
pub server_result: BarkBattleServerResult,
|
|
pub score_summary: BarkBattleScoreSummary,
|
|
pub leaderboard_score: u64,
|
|
pub updated_at: String,
|
|
}
|
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct BarkBattleLeaderboardResponse {
|
|
pub work_id: String,
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub config_version: Option<u32>,
|
|
pub ruleset_version: String,
|
|
pub difficulty_preset: BarkBattleDifficultyPreset,
|
|
#[serde(default)]
|
|
pub entries: Vec<BarkBattleLeaderboardEntry>,
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub viewer_best: Option<BarkBattleLeaderboardEntry>,
|
|
pub updated_at: String,
|
|
}
|
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct BarkBattlePersonalHistoryItem {
|
|
pub run_id: String,
|
|
pub work_id: String,
|
|
pub config_version: u32,
|
|
pub ruleset_version: String,
|
|
pub difficulty_preset: BarkBattleDifficultyPreset,
|
|
pub server_result: BarkBattleServerResult,
|
|
pub score_summary: BarkBattleScoreSummary,
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub leaderboard_score: Option<u64>,
|
|
#[serde(default)]
|
|
pub anti_cheat_flags: Vec<String>,
|
|
pub updated_at: String,
|
|
}
|
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct BarkBattlePersonalBestSummary {
|
|
pub work_id: String,
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub config_version: Option<u32>,
|
|
pub ruleset_version: String,
|
|
pub difficulty_preset: BarkBattleDifficultyPreset,
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub best_leaderboard_score: Option<u64>,
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub best_final_energy: Option<f32>,
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub best_trigger_count: Option<u32>,
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub best_max_volume: Option<f32>,
|
|
pub win_count: u64,
|
|
pub draw_count: u64,
|
|
pub loss_count: u64,
|
|
pub finish_count: u64,
|
|
pub updated_at: String,
|
|
}
|
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct BarkBattlePersonalHistoryResponse {
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub work_id: Option<String>,
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub difficulty_preset: Option<BarkBattleDifficultyPreset>,
|
|
#[serde(default)]
|
|
pub items: Vec<BarkBattlePersonalHistoryItem>,
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub best_summary: Option<BarkBattlePersonalBestSummary>,
|
|
pub updated_at: String,
|
|
}
|
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct BarkBattleWorkStats {
|
|
pub work_id: String,
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub config_version: Option<u32>,
|
|
pub ruleset_version: String,
|
|
pub difficulty_preset: BarkBattleDifficultyPreset,
|
|
pub play_start_count: u64,
|
|
pub finish_count: u64,
|
|
pub win_count: u64,
|
|
pub draw_count: u64,
|
|
pub loss_count: u64,
|
|
pub flagged_count: u64,
|
|
pub leaderboard_entry_count: u64,
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub best_leaderboard_score: Option<u64>,
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub best_final_energy: Option<f32>,
|
|
#[serde(default, skip_serializing_if = "Option::is_none")]
|
|
pub average_final_energy: Option<f32>,
|
|
pub updated_at: String,
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use serde_json::json;
|
|
|
|
#[test]
|
|
fn editor_and_runtime_contract_use_description_fields_only() {
|
|
let editor = BarkBattleConfigEditorPayload {
|
|
title: "周末狗狗杯".to_string(),
|
|
description: Some("轻配置草稿".to_string()),
|
|
theme_description: "霓虹公园里的欢乐擂台".to_string(),
|
|
player_image_description: "戴红围巾的柴犬主角".to_string(),
|
|
opponent_image_description: "蓝色护目镜哈士奇对手".to_string(),
|
|
onomatopoeia: Some(vec!["轰汪!".to_string(), "炸场!".to_string()]),
|
|
player_character_image_src: Some("/generated-bark-battle/player.png".to_string()),
|
|
opponent_character_image_src: Some("https://example.test/opponent.png".to_string()),
|
|
ui_background_image_src: Some("/generated-bark-battle/ui.png".to_string()),
|
|
difficulty_preset: BarkBattleDifficultyPreset::Hard,
|
|
};
|
|
let payload = serde_json::to_value(editor).expect("config should serialize");
|
|
|
|
assert_eq!(payload["themeDescription"], json!("霓虹公园里的欢乐擂台"));
|
|
assert_eq!(
|
|
payload["playerImageDescription"],
|
|
json!("戴红围巾的柴犬主角")
|
|
);
|
|
assert_eq!(
|
|
payload["opponentImageDescription"],
|
|
json!("蓝色护目镜哈士奇对手")
|
|
);
|
|
assert_eq!(payload["onomatopoeia"], json!(["轰汪!", "炸场!"]));
|
|
for removed in [
|
|
"themePreset",
|
|
"playerDogSkinPreset",
|
|
"opponentDogSkinPreset",
|
|
"barkSoundSrc",
|
|
"leaderboardEnabled",
|
|
] {
|
|
assert!(
|
|
!payload.as_object().unwrap().contains_key(removed),
|
|
"{removed} must not remain in v1 public config payload"
|
|
);
|
|
}
|
|
|
|
let runtime = BarkBattleRuntimeConfig {
|
|
work_id: "bark-battle-work-1".to_string(),
|
|
config_version: 1,
|
|
ruleset_version: "bark-battle-ruleset-v1".to_string(),
|
|
play_type_id: "bark-battle".to_string(),
|
|
duration_ms: 30_000,
|
|
energy_min: 0.0,
|
|
energy_max: 100.0,
|
|
draw_threshold: 5.0,
|
|
min_bark_gap_ms: 220,
|
|
difficulty_preset: BarkBattleDifficultyPreset::Normal,
|
|
theme_description: "阳光草坪".to_string(),
|
|
player_image_description: "小柴犬".to_string(),
|
|
opponent_image_description: "大金毛".to_string(),
|
|
onomatopoeia: Some(vec!["轰汪!".to_string(), "燃起来!".to_string()]),
|
|
player_character_image_src: None,
|
|
opponent_character_image_src: None,
|
|
ui_background_image_src: None,
|
|
updated_at: "2026-05-20T00:00:00Z".to_string(),
|
|
};
|
|
let payload = serde_json::to_value(runtime).expect("runtime should serialize");
|
|
assert_eq!(payload["themeDescription"], json!("阳光草坪"));
|
|
assert!(
|
|
!payload
|
|
.as_object()
|
|
.unwrap()
|
|
.contains_key("leaderboardEnabled")
|
|
);
|
|
assert!(!payload.as_object().unwrap().contains_key("barkSoundSrc"));
|
|
}
|
|
|
|
#[test]
|
|
fn work_summary_responses_use_public_gallery_contract() {
|
|
let response = BarkBattleWorksResponse {
|
|
items: vec![BarkBattleWorkSummary {
|
|
work_id: "bark-battle-work-1".to_string(),
|
|
draft_id: Some("bark-battle-draft-1".to_string()),
|
|
owner_user_id: "user-1".to_string(),
|
|
author_display_name: "玩家".to_string(),
|
|
title: "汪汪测试杯".to_string(),
|
|
summary: "轻量公开卡片".to_string(),
|
|
theme_description: "阳光草坪".to_string(),
|
|
player_image_description: "小柴犬".to_string(),
|
|
opponent_image_description: "大金毛".to_string(),
|
|
onomatopoeia: Some(vec!["轰汪!".to_string(), "燃起来!".to_string()]),
|
|
player_character_image_src: None,
|
|
opponent_character_image_src: None,
|
|
ui_background_image_src: None,
|
|
difficulty_preset: BarkBattleDifficultyPreset::Normal,
|
|
status: "published".to_string(),
|
|
generation_status: Some("ready".to_string()),
|
|
publish_ready: true,
|
|
play_count: 3,
|
|
finish_count: Some(2),
|
|
win_count: Some(1),
|
|
draw_count: Some(1),
|
|
loss_count: Some(0),
|
|
recent_play_count_7d: Some(2),
|
|
updated_at: "2026-05-20T00:00:00Z".to_string(),
|
|
published_at: Some("2026-05-20T00:00:00Z".to_string()),
|
|
}],
|
|
};
|
|
|
|
let payload = serde_json::to_value(response).expect("works response should serialize");
|
|
|
|
assert_eq!(payload["items"][0]["themeDescription"], json!("阳光草坪"));
|
|
assert_eq!(payload["items"][0]["recentPlayCount7d"], json!(2));
|
|
assert_eq!(payload["items"][0]["status"], json!("published"));
|
|
}
|
|
|
|
#[test]
|
|
fn draft_config_defaults_to_normal_difficulty() {
|
|
let config = BarkBattleDraftConfig::default();
|
|
|
|
assert_eq!(config.difficulty_preset, BarkBattleDifficultyPreset::Normal);
|
|
}
|
|
|
|
#[test]
|
|
fn run_requests_carry_config_and_ruleset_identity() {
|
|
let start = BarkBattleRunStartRequest {
|
|
work_id: "work-1".to_string(),
|
|
config_version: Some(3),
|
|
source_route: Some("gallery".to_string()),
|
|
client_runtime_version: Some("runtime-v1".to_string()),
|
|
};
|
|
let start_payload = serde_json::to_value(start).expect("start request should serialize");
|
|
assert_eq!(start_payload["workId"], json!("work-1"));
|
|
assert_eq!(start_payload["configVersion"], json!(3));
|
|
assert_eq!(start_payload["sourceRoute"], json!("gallery"));
|
|
|
|
let finish = BarkBattleRunFinishRequest {
|
|
work_id: "work-1".to_string(),
|
|
run_id: "run-1".to_string(),
|
|
run_token: "token-1".to_string(),
|
|
config_version: 3,
|
|
ruleset_version: "bark-battle-ruleset-v1".to_string(),
|
|
difficulty_preset: BarkBattleDifficultyPreset::Hard,
|
|
client_started_at: "2026-05-13T11:00:00Z".to_string(),
|
|
client_finished_at: "2026-05-13T11:00:30Z".to_string(),
|
|
duration_ms: 30_000,
|
|
derived_metrics: BarkBattleDerivedMetrics {
|
|
trigger_count: 12,
|
|
max_volume: 0.95,
|
|
average_volume: 0.62,
|
|
final_energy: 88.5,
|
|
combo_max: 7,
|
|
},
|
|
client_result: Some(BarkBattleServerResult::PlayerWin),
|
|
sample_digest: Some("digest-1".to_string()),
|
|
client_runtime_version: None,
|
|
};
|
|
let finish_payload = serde_json::to_value(finish).expect("finish request should serialize");
|
|
assert_eq!(finish_payload["configVersion"], json!(3));
|
|
assert_eq!(
|
|
finish_payload["rulesetVersion"],
|
|
json!("bark-battle-ruleset-v1")
|
|
);
|
|
assert_eq!(finish_payload["difficultyPreset"], json!("hard"));
|
|
}
|
|
|
|
#[test]
|
|
fn optional_fields_are_omitted_when_absent() {
|
|
let draft = BarkBattleDraftConfig::default();
|
|
let payload = serde_json::to_value(draft).expect("draft should serialize");
|
|
assert!(!payload.as_object().unwrap().contains_key("workId"));
|
|
assert!(!payload.as_object().unwrap().contains_key("configVersion"));
|
|
assert!(!payload.as_object().unwrap().contains_key("rulesetVersion"));
|
|
assert!(!payload.as_object().unwrap().contains_key("description"));
|
|
assert!(
|
|
!payload
|
|
.as_object()
|
|
.unwrap()
|
|
.contains_key("playerCharacterImageSrc")
|
|
);
|
|
assert!(
|
|
!payload
|
|
.as_object()
|
|
.unwrap()
|
|
.contains_key("uiBackgroundImageSrc")
|
|
);
|
|
|
|
let response = BarkBattlePersonalHistoryResponse {
|
|
work_id: None,
|
|
difficulty_preset: None,
|
|
items: Vec::new(),
|
|
best_summary: None,
|
|
updated_at: "2026-05-13T11:00:00Z".to_string(),
|
|
};
|
|
let payload = serde_json::to_value(response).expect("history response should serialize");
|
|
assert!(!payload.as_object().unwrap().contains_key("workId"));
|
|
assert!(
|
|
!payload
|
|
.as_object()
|
|
.unwrap()
|
|
.contains_key("difficultyPreset")
|
|
);
|
|
assert!(!payload.as_object().unwrap().contains_key("bestSummary"));
|
|
}
|
|
|
|
#[test]
|
|
fn draft_config_serializes_persistent_identity_fields() {
|
|
let draft = BarkBattleDraftConfig {
|
|
draft_id: "bark-battle-draft-1".to_string(),
|
|
work_id: Some("bark-battle-work-1".to_string()),
|
|
config_version: Some(2),
|
|
ruleset_version: Some("bark-battle-ruleset-v1".to_string()),
|
|
title: "汪汪测试杯".to_string(),
|
|
description: None,
|
|
theme_description: "阳光草坪".to_string(),
|
|
player_image_description: "主角".to_string(),
|
|
opponent_image_description: "对手".to_string(),
|
|
onomatopoeia: None,
|
|
player_character_image_src: None,
|
|
opponent_character_image_src: None,
|
|
ui_background_image_src: None,
|
|
difficulty_preset: BarkBattleDifficultyPreset::Normal,
|
|
updated_at: "2026-05-14T10:00:00.000Z".to_string(),
|
|
};
|
|
|
|
let payload = serde_json::to_value(draft).expect("draft should serialize");
|
|
|
|
assert_eq!(payload["draftId"], json!("bark-battle-draft-1"));
|
|
assert_eq!(payload["workId"], json!("bark-battle-work-1"));
|
|
assert_eq!(payload["configVersion"], json!(2));
|
|
assert_eq!(payload["rulesetVersion"], json!("bark-battle-ruleset-v1"));
|
|
}
|
|
|
|
#[test]
|
|
fn draft_config_update_request_serializes_generated_assets() {
|
|
let update = BarkBattleDraftConfigUpdateRequest {
|
|
draft_id: "bark-battle-draft-1".to_string(),
|
|
work_id: Some("BB-12345678".to_string()),
|
|
config_version: Some(2),
|
|
ruleset_version: Some("bark-battle-ruleset-v1".to_string()),
|
|
title: "汪汪测试杯".to_string(),
|
|
description: None,
|
|
theme_description: "阳光草坪".to_string(),
|
|
player_image_description: "主角".to_string(),
|
|
opponent_image_description: "对手".to_string(),
|
|
onomatopoeia: Some(vec!["炸场!".to_string(), "破阵!".to_string()]),
|
|
player_character_image_src: Some("/generated-bark-battle/player.png".to_string()),
|
|
opponent_character_image_src: Some("/generated-bark-battle/opponent.png".to_string()),
|
|
ui_background_image_src: Some("/generated-bark-battle/background.png".to_string()),
|
|
difficulty_preset: BarkBattleDifficultyPreset::Normal,
|
|
};
|
|
|
|
let payload = serde_json::to_value(update).expect("draft update should serialize");
|
|
|
|
assert_eq!(payload["draftId"], json!("bark-battle-draft-1"));
|
|
assert_eq!(payload["workId"], json!("BB-12345678"));
|
|
assert_eq!(
|
|
payload["playerCharacterImageSrc"],
|
|
json!("/generated-bark-battle/player.png")
|
|
);
|
|
assert_eq!(
|
|
payload["opponentCharacterImageSrc"],
|
|
json!("/generated-bark-battle/opponent.png")
|
|
);
|
|
assert_eq!(
|
|
payload["uiBackgroundImageSrc"],
|
|
json!("/generated-bark-battle/background.png")
|
|
);
|
|
assert!(!payload.as_object().unwrap().contains_key("barkSoundSrc"));
|
|
assert!(
|
|
!payload
|
|
.as_object()
|
|
.unwrap()
|
|
.contains_key("leaderboardEnabled")
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn image_generation_request_uses_dedicated_asset_slot_and_result_prompt() {
|
|
let request = BarkBattleImageAssetGenerateRequest {
|
|
slot: BarkBattleAssetSlot::OpponentCharacter,
|
|
draft_id: Some("bark-battle-draft-1".to_string()),
|
|
config: BarkBattleConfigEditorPayload {
|
|
title: "汪汪冠军杯".to_string(),
|
|
description: Some(String::new()),
|
|
theme_description: "霓虹公园擂台".to_string(),
|
|
player_image_description: "红围巾柴犬".to_string(),
|
|
opponent_image_description: "蓝头带哈士奇".to_string(),
|
|
onomatopoeia: Some(vec!["轰汪!".to_string(), "炸场!".to_string()]),
|
|
player_character_image_src: None,
|
|
opponent_character_image_src: None,
|
|
ui_background_image_src: None,
|
|
difficulty_preset: BarkBattleDifficultyPreset::Normal,
|
|
},
|
|
};
|
|
let payload = serde_json::to_value(request).expect("request should serialize");
|
|
|
|
assert_eq!(payload["slot"], json!("opponent-character"));
|
|
assert_eq!(
|
|
payload["config"]["opponentImageDescription"],
|
|
json!("蓝头带哈士奇")
|
|
);
|
|
|
|
let response = BarkBattleGeneratedImageAsset {
|
|
image_src: "/generated-bark-battle-assets/draft/opponent/image.webp".to_string(),
|
|
asset_id: "asset-1".to_string(),
|
|
source_type: Some("generated".to_string()),
|
|
model: "gpt-image-2".to_string(),
|
|
size: "1024*1024".to_string(),
|
|
task_id: "task-1".to_string(),
|
|
prompt: "后端拼装后的对手形象 prompt".to_string(),
|
|
actual_prompt: None,
|
|
};
|
|
let payload = serde_json::to_value(response).expect("response should serialize");
|
|
|
|
assert_eq!(
|
|
payload["imageSrc"],
|
|
json!("/generated-bark-battle-assets/draft/opponent/image.webp")
|
|
);
|
|
assert_eq!(payload["prompt"], json!("后端拼装后的对手形象 prompt"));
|
|
}
|
|
|
|
#[test]
|
|
fn replacement_sources_serialize_as_camel_case_config_fields() {
|
|
let config = BarkBattleConfigEditorPayload {
|
|
title: "周末狗狗杯".to_string(),
|
|
description: Some("轻配置草稿".to_string()),
|
|
theme_description: "霓虹公园".to_string(),
|
|
player_image_description: "柴犬主角".to_string(),
|
|
opponent_image_description: "哈士奇对手".to_string(),
|
|
onomatopoeia: Some(vec!["轰汪!".to_string(), "冲啊!".to_string()]),
|
|
player_character_image_src: Some("/generated-bark-battle/player.png".to_string()),
|
|
opponent_character_image_src: Some("https://example.test/opponent.png".to_string()),
|
|
ui_background_image_src: Some("/generated-bark-battle/ui.png".to_string()),
|
|
difficulty_preset: BarkBattleDifficultyPreset::Hard,
|
|
};
|
|
|
|
let payload = serde_json::to_value(config).expect("config should serialize");
|
|
|
|
assert_eq!(
|
|
payload["playerCharacterImageSrc"],
|
|
json!("/generated-bark-battle/player.png")
|
|
);
|
|
assert_eq!(
|
|
payload["opponentCharacterImageSrc"],
|
|
json!("https://example.test/opponent.png")
|
|
);
|
|
assert_eq!(
|
|
payload["uiBackgroundImageSrc"],
|
|
json!("/generated-bark-battle/ui.png")
|
|
);
|
|
assert!(!payload.as_object().unwrap().contains_key("barkSoundSrc"));
|
|
}
|
|
|
|
#[test]
|
|
fn finish_response_serializes_player_win_and_accepted() {
|
|
let response = BarkBattleRunFinishResponse {
|
|
status: BarkBattleFinishStatus::Accepted,
|
|
run_id: "run-1".to_string(),
|
|
work_id: "work-1".to_string(),
|
|
config_version: 3,
|
|
ruleset_version: "bark-battle-ruleset-v1".to_string(),
|
|
difficulty_preset: BarkBattleDifficultyPreset::Normal,
|
|
server_result: BarkBattleServerResult::PlayerWin,
|
|
score_summary: BarkBattleScoreSummary {
|
|
duration_ms: 30_000,
|
|
trigger_count: 12,
|
|
max_volume: 0.95,
|
|
average_volume: 0.62,
|
|
final_energy: 88.5,
|
|
combo_max: 7,
|
|
},
|
|
leaderboard_score: Some(98_765),
|
|
anti_cheat_flags: Vec::new(),
|
|
updated_at: "2026-05-13T11:00:00Z".to_string(),
|
|
};
|
|
|
|
let payload = serde_json::to_value(response).expect("finish response should serialize");
|
|
|
|
assert_eq!(payload["runId"], json!("run-1"));
|
|
assert_eq!(payload["status"], json!("accepted"));
|
|
assert_eq!(payload["serverResult"], json!("player_win"));
|
|
assert_eq!(payload["leaderboardScore"], json!(98_765));
|
|
assert_eq!(payload["scoreSummary"]["finalEnergy"], json!(88.5));
|
|
}
|
|
|
|
#[test]
|
|
fn work_stats_fields_are_constructible() {
|
|
let stats = BarkBattleWorkStats {
|
|
work_id: "work-1".to_string(),
|
|
config_version: Some(3),
|
|
ruleset_version: "bark-battle-ruleset-v1".to_string(),
|
|
difficulty_preset: BarkBattleDifficultyPreset::Normal,
|
|
play_start_count: 10,
|
|
finish_count: 9,
|
|
win_count: 5,
|
|
draw_count: 2,
|
|
loss_count: 2,
|
|
flagged_count: 1,
|
|
leaderboard_entry_count: 4,
|
|
best_leaderboard_score: Some(98_765),
|
|
best_final_energy: Some(97.5),
|
|
average_final_energy: Some(73.25),
|
|
updated_at: "2026-05-13T11:00:00Z".to_string(),
|
|
};
|
|
|
|
assert_eq!(stats.work_id, "work-1");
|
|
assert_eq!(stats.play_start_count, 10);
|
|
assert_eq!(stats.finish_count, 9);
|
|
assert_eq!(stats.win_count, 5);
|
|
assert_eq!(stats.draw_count, 2);
|
|
assert_eq!(stats.loss_count, 2);
|
|
assert_eq!(stats.flagged_count, 1);
|
|
assert_eq!(stats.leaderboard_entry_count, 4);
|
|
assert_eq!(stats.best_leaderboard_score, Some(98_765));
|
|
assert_eq!(stats.best_final_energy, Some(97.5));
|
|
assert_eq!(stats.average_final_energy, Some(73.25));
|
|
assert_eq!(stats.updated_at, "2026-05-13T11:00:00Z");
|
|
}
|
|
}
|