rusty_uma_extractor 0.1.0

A set of useful utility modules for applications developed to be used with the game Umamusume: Pretty Derby.
use std::{
    error::{self, Error},
    fs::OpenOptions,
    io::{ self, LineWriter, Write },
    path::Path,
    result
};
use serde::{Deserialize, Serialize};
use serde_json;

use super::roster_extractor::TrainedCharacterData;

pub type ViewerId = u64;
pub type TrainedCharaId = u16;
pub type FactorId = u32;
pub type CardId = u32;
pub type WinSaddleId = u32;
pub type NicknameId = u32;
pub type Rank = u8;
pub type Rarity = u8;
pub type TalentLevel = u8;

/// Object structure for race veteran participated in during scenario

#[derive(Debug, PartialEq, Serialize, Deserialize)]
pub struct RaceResult {
    /// Value for enumeration of turn number the race took place in scenario. Note that the first race will always be turn 12 which is the first Debut Race
    pub turn: u8,
    /// UID mapping to `Race` object in `data_structures`. See `data_extractor` module for more info
    pub program_id: u32,
    /// Value for enumeration of weather conditions during race, where 1 = sunny, 2 = cloudy, 3 = rainy, 4 = snowy
    pub weather: u8,
    /// Value for enumeration of track conditions during race, where 1 = firm, 2 = good, 3 = soft, 4 = heavy
    pub ground_condition: u8,
    /// Value for running style used during race as enumeration where 1 = Front Runner,....,4 = End Closer
    pub running_style: u8,
    /// Value for popularity placement during race, where 1 = most popular
    pub popularity: u8,
    /// Value for position placement after crossing finish line, where 1 = first
    pub result_rank: u8,
    /// Value for result time after crossing finish line. Value unit appears to most likely be μs?
    pub result_time: u32,
    /// Value for prize money obtained after race. I believe this is currently an unused value as it is always equal to 0
    pub prize_money: u8
}

/// Object structure for support card used during veteran scenario

#[derive(Debug, PartialEq, Serialize, Deserialize)]
pub struct SupportCard {
    /// Value for enumeration of support card position in deck
    pub position: u8,
    /// UID mapping to `SupportCard` object in `data_structures`. See `data_extractor` module for more info
    pub support_card_id: u32,
    /// Value for experience points the support card had at time of use for veteran campaign
    pub exp: u32,
    /// Value for enumeration of support card limit break level where 0 = 0LB,...,4 = MLB
    pub limit_break_count: u8
}

/// Object structure for skills the veteran obtained

#[derive(Debug, PartialEq, Serialize, Deserialize)]
pub struct Skill {
    /// UID mapping to `Skill` object in `data_structures`. See `data_extractor` module for more info
    pub skill_id: u32,
    /// Value for enumeration of skill level starting at 1 = Level 1
    pub level: u8
}

/// Object structure for sparks the veteran obtained.

#[derive(Debug, PartialEq, Serialize, Deserialize)]
pub struct FactorInfo {
    /// UID mapping to `Factor` object in `data_structures`. See `data_extractor` module for more info
    pub factor_id: FactorId,
    /// Unknown value function. Appears to always be equal to 0
    pub level: u8
}

/// Object structure for other veterans this character was used as inspiration for.

#[derive(Debug, PartialEq, Serialize, Deserialize)]
pub struct SuccessionHistory {
    /// UID for this specific object record
    pub id: u32,
    viewer_id: ViewerId,
    /// UID for this particular Veteran character instance. Could be user's own character or another user's in which this veteran was borrowed for
    pub trained_chara_id: TrainedCharaId,
    /// Unknown value function
    pub history_type: u8,
    /// UID mapping to `CharacterCard` in `data_structures`. See `data_extractor` module for more info
    pub succession_card_id: u32,
    /// Date value when character was used for succession (YYYYMMDD)
    pub date: u32,
    rental_viewer_id: u64,
    /// User name of user who borrowed character. Empty string if Self
    pub user_name: String,
    /// Club name of user who borrowed character. Empty string if Self
    pub circle_name: String
}

/// Object structure for legacies and grand-legacies used for
/// veteran.

#[derive(Debug, PartialEq, Serialize, Deserialize)]
pub struct SuccessionCharacter {
    /// Value to indicate hierarchy position of legacy where: 1x = veteran 1 hierarchy tree; 2x = veteran 2 hierarchy tree;
    pub position_id: u8,
    /// UID mapping to `CharacterCard` in `data_structures`. See `data_extractor` module for more info
    pub card_id: CardId,
    /// Value for rank enumeration where 0 = G,....,17 = SS+
    pub rank: Rank,
    /// Value for number of stars
    pub rarity: Rarity,
    /// Value for Potential level
    pub talent_level: TalentLevel,
    /// List of `FactorId` values
    pub factor_id_array: Vec<FactorId>,
    /// List of `FactorInfo` objects 
    pub factor_info_array: Vec<FactorInfo>,
    /// List of `WinSaddleId` values
    pub win_saddle_id_array: Vec<WinSaddleId>,
    owner_viewer_id: ViewerId
}

/// The root object structure that contains all the information for a single
/// instance of a Veteran character.

#[derive(Debug, PartialEq, Serialize, Deserialize)]
pub struct TrainedCharacter {
   viewer_id: ViewerId,
   /// UID for this particular Veteran character instance
   pub trained_chara_id: TrainedCharaId,
   owner_viewer_id: ViewerId,
   /// UID for this particular Veteran character instance from the owner of the character. This particular value will always be 0 due to implicit Self owner
   pub owner_trained_chara_id: u16,
   /// UID mapping to `single_mode_chara_program` table in `master.mdb`
   pub single_mode_chara_id: u8,
   /// Unknown value function. Most likely used to seed the RNG for the scenario this particular character completed
   pub chara_seed: u64,
   /// UID mapping to `card_data` (aka Character Card) table in `master.mdb`. See `data_extractor` modules for more information
   pub card_id: CardId,
   /// `trained_chara_id` for Veteran 1 used for inspiration. Can either be user's own character or borrowed
   pub succession_trained_chara_id_1: u32,
   /// `trained_chara_id` for Veteran 2 used for inspiration. Can either be user's own character or borrowed
   pub succession_trained_chara_id_2: u32,
   /// Unknown value function. This value is always 0
   pub use_type: u8,
   /// Speed stat value
   pub speed: u16,
   /// Stamina stat value
   pub stamina: u16,
   /// Power stat value
   pub power: u16,
   /// Wits stat value
   pub wiz: u16,
   /// Guts stat value
   pub guts: u16,
   /// Total number of fans gained in character campaign
   pub fans: u64,
   /// Rank score value
   pub rank_score: u32,
   /// Value for rank enumeration where 0 = G,....,17 = SS+
   pub rank: Rank,
   /// UID for scenario type
   pub scenario_id: u16,
   /// UID for `single_mode_route` table in `master.mdb`
   pub route_id: u16,
   /// UID for `single_mode_route_race` table in `master.mdb`
   pub arrive_route_race_id: u16,
   /// Value for proficiency grade enumeration in Turf where 1 = G,....,8 = S
   pub proper_ground_turf: u8,
   /// Value for proficiency grade enumeration in Dirt where 1 = G,....,8 = S
   pub proper_ground_dirt: u8,
   /// Value for proficiency grade enumeration in Front Runner where 1 = G,....,8 = S
   pub proper_running_style_nige: u8,
   /// Value for proficiency grade enumeration in Pace Chaser where 1 = G,....,8 = S
   pub proper_running_style_senko: u8,
   /// Value for proficiency grade enumeration in Late Surger where 1 = G,....,8 = S
   pub proper_running_style_sashi: u8,
   /// Value for proficiency grade enumeration in End Closer where 1 = G,....,8 = S
   pub proper_running_style_oikomi: u8,
   /// Value for proficiency grade enumeration in Sprint where 1 = G,....,8 = S
   pub proper_distance_short: u8,
   /// Value for proficiency grade enumeration in Mile where 1 = G,....,8 = S
   pub proper_distance_mile: u8,
   /// Value for proficiency grade enumeration in Medium where 1 = G,....,8 = S
   pub proper_distance_middle: u8,
   /// Value for proficiency grade enumeration in Long where 1 = G,....,8 = S
   pub proper_distance_long: u8,
   /// Value for how many times veteran used for inspiration
   pub succession_num: u8,
   /// Value for number of stars
   pub rarity: Rarity,
   /// Unknown value function
   pub is_saved: u8,
   /// Unknown value function
   pub is_locked: u8,
   /// Value for Potential level
   pub talent_level: TalentLevel,
   /// UID for some table in `master.mdb`, just not sure which?
   pub race_cloth_id: u32,
   /// Unknown value function
   pub chara_grade: u16,
   /// Value for default running style enumeration where 1 = Front Runner,....,4 = End Closer
   pub running_style: u8,
   /// UID for `nickname` table in `master.mdb`
   pub nickname_id: NicknameId,
   /// Value for number of total wins
   pub wins: u8,
   /// UTC timestamp for character registration to server. Usually equal to `create_time`
   pub register_time: String,
   /// UTC timestamp for character creation. Usually equal to `register_time`
   pub create_time: String,
   /// List of `Skill` objects
   pub skill_array: Vec<Skill>,
   /// List of `SupportCard` objects
   pub support_card_list: Vec<SupportCard>,
   /// List of `RaceResult` objects
   pub race_result_list: Vec<RaceResult>,
   /// List of `WinSaddleId` values
   pub win_saddle_id_array: Vec<WinSaddleId>,
   /// List of `NicknameId` values
   pub nickname_id_array: Vec<NicknameId>,
   /// List of `FactorId` values
   pub factor_id_array: Vec<FactorId>,
   /// List of `FactorInfo` objects
   pub factor_info_array: Vec<FactorInfo>,
   /// List of `SuccessionCharacter` objects. Should always expect a length of 6 (2 legacies + 4 grand-legacies)
   pub succession_chara_array: Vec<SuccessionCharacter>,
   /// List of `SuccessionHistory` objects
   pub succession_history_array: Vec<SuccessionHistory>
}

/// Deserializes trained character data after successfully pulling from memory map.
/// 
/// # Params
/// | name | type | description |
/// | -- | -- | -- |
/// | `data` | `TrainedCharacterData` | Byte data vector array of trained character data extracted from memory region. |

pub fn deserialize_data(data: TrainedCharacterData) -> Result<Vec<TrainedCharacter>,rmp_serde::decode::Error> {
    match rmp_serde::from_slice::<Vec<TrainedCharacter>>(&data) {
                Ok(d) => return Ok(d),
                Err(e) => return Err(e)
            };
}

/// Outputs a formatted JSON file to a specified directory from vector array
/// of `TrainedCharacter` objects. 
/// 
/// First it will attempt to create a new file at this location,
/// if that operation fails, it will attempt to write to existing file and perform the `truncate` operation.
/// If all else fails, expect an `io::Error` to be returned. It is typically common convention to name this file `data.json`.
/// 
/// _**NOTE**: This function sanitizes the fields `viewer_id`, `owner_viewer_id`, and `rental_viewer_id` by replacing the values with zeros.
///             This was done for privacy reasons as stated in the original Python code so it was implemented here as well._
///
/// # Params
/// | name | type | description |
/// | -- | -- | -- |
/// | `deser_data` | `&mut Vec<TrainedCharacter>` | Mutably borrowed vector array of `TrainedCharacter` objects. |
/// | `path` | `&Path` | A borrowed Path containing the full directory path, file name and extension to output file into. |


pub fn to_json_file(deser_data: &mut Vec<TrainedCharacter>, path: &Path) -> io::Result<()> {

    for card in deser_data.iter_mut() {
        card.viewer_id = 0;
        card.owner_viewer_id = 0;
        let _ = card.succession_chara_array.iter_mut()
        .for_each(|s_char| s_char.owner_viewer_id = 0);
        let _ = card.succession_history_array.iter_mut()
        .for_each(|s_hist| { 
            s_hist.viewer_id = 0;
            s_hist.rental_viewer_id = 0;
        });
    }

    let json_string = match serde_json::to_string_pretty(&deser_data) {
        Ok(str) => str,
        Err(e) => return Err(io::Error::new(io::ErrorKind::Other, e))
    };

    match OpenOptions::new().write(true).create_new(true).open(path) {
        Ok(outfile) => {
            let mut line_writer = LineWriter::new(outfile);
            line_writer.write_all(json_string.as_bytes())?;
            line_writer.flush()?;
            println!("Wrote file to: {}",path.display());
        },
        Err(err) => match err.kind() {
            io::ErrorKind::AlreadyExists => match OpenOptions::new().write(true).truncate(true).open(path) {
                Ok(outfile) => {
                    let mut line_writer = LineWriter::new(outfile);
                    line_writer.write_all(json_string.as_bytes())?;
                    line_writer.flush()?;
                    println!("Wrote file to: {}",path.display());
                },
                Err(e) => return Err(e)
            },
            e @ _ => return Err(io::Error::from(e))
        }
    }

    Ok(())
}