uxie 0.6.3

Data fetching library for Pokemon Gen 4 romhacking - map headers, C parsing, and more
Documentation
use uxie::encounter_file::json::WaterEncounterEntryJson;
use uxie::{GameFamily, JsonEncounterFile, JsonEventFile, MapHeader, SymbolTable, Workspace};

fn resolve_map_symbol(symbols: &SymbolTable, val: i64, prefix: &str) -> String {
    symbols
        .resolve_name(val, prefix)
        .unwrap_or_else(|| val.to_string())
}

fn print_map_header_intro(
    header: &MapHeader,
    id: u16,
    ws: &Workspace,
    game_name: &str,
    divider_len: usize,
) {
    let internal_name = ws
        .get_map_internal_name(id)
        .unwrap_or_else(|| "Unknown".to_string());
    let location_id = match header {
        MapHeader::DP(h) => h.location_name,
        MapHeader::Pt(h) => h.location_name as u16,
        MapHeader::HGSS(h) => h.location_name as u16,
    };
    let pretty_name = ws
        .get_map_location_name(location_id as u8)
        .unwrap_or_else(|| "Unknown".to_string());

    println!("Map Header {} ({})", id, game_name);
    println!("Internal Name:   {}", internal_name);
    println!("Pretty Name:     {}", pretty_name);
    println!("{}", "=".repeat(divider_len));
    println!("Area Data ID:    {}", header.area_data_id());
    println!("Matrix ID:       {}", header.matrix_id());
    println!("Script File ID:  {}", header.script_file_id());
    println!("Level Script ID: {}", header.level_script_id());
    println!("Text Archive ID: {}", header.text_archive_id());
}

fn print_map_header_dp(h: &uxie::map_header::MapHeaderDP, symbols: &SymbolTable) {
    println!(
        "Music Day:       {}",
        resolve_map_symbol(symbols, h.music_day_id as i64, "SEQ_")
    );
    println!(
        "Music Night:     {}",
        resolve_map_symbol(symbols, h.music_night_id as i64, "SEQ_")
    );
    println!("Wild Pokemon:    {}", h.wild_pokemon);
    println!("Event File ID:   {}", h.event_file_id);
    println!(
        "Location Name:   {}",
        resolve_map_symbol(symbols, h.location_name as i64, "MAPSEC_")
    );
    println!(
        "Weather:         {}",
        resolve_map_symbol(symbols, h.weather_id as i64, "OVERWORLD_WEATHER_")
    );
    println!(
        "Camera:          {}",
        resolve_map_symbol(symbols, h.camera_angle_id as i64, "CAMERA_TYPE_")
    );
    println!(
        "Battle BG:       {}",
        resolve_map_symbol(symbols, h.battle_background as i64, "BATTLE_BG_")
    );
    println!("Flags:           0x{:02X}", h.flags);
}

fn print_map_header_pt(h: &uxie::map_header::MapHeaderPt, symbols: &SymbolTable) {
    println!(
        "Music Day:       {}",
        resolve_map_symbol(symbols, h.music_day_id as i64, "SEQ_")
    );
    println!(
        "Music Night:     {}",
        resolve_map_symbol(symbols, h.music_night_id as i64, "SEQ_")
    );
    println!("Wild Pokemon:    {}", h.wild_pokemon);
    println!("Event File ID:   {}", h.event_file_id);
    println!(
        "Location Name:   {}",
        resolve_map_symbol(symbols, h.location_name as i64, "MAPSEC_")
    );
    println!("Area Icon:       {}", h.area_icon);
    println!(
        "Weather:         {}",
        resolve_map_symbol(symbols, h.weather_id as i64, "OVERWORLD_WEATHER_")
    );
    println!(
        "Camera:          {}",
        resolve_map_symbol(symbols, h.camera_angle_id as i64, "CAMERA_TYPE_")
    );
    println!(
        "Battle BG:       {}",
        resolve_map_symbol(symbols, h.battle_background as i64, "BATTLE_BG_")
    );
    println!("Flags:           0x{:02X}", h.flags);
}

fn print_map_header_hgss(h: &uxie::map_header::MapHeaderHGSS, symbols: &SymbolTable) {
    println!(
        "Music Day:       {}",
        resolve_map_symbol(symbols, h.music_day_id as i64, "SEQ_")
    );
    println!(
        "Music Night:     {}",
        resolve_map_symbol(symbols, h.music_night_id as i64, "SEQ_")
    );
    println!("Wild Pokemon:    {}", h.wild_pokemon);
    println!("Event File ID:   {}", h.event_file_id);
    println!(
        "Location Name:   {}",
        resolve_map_symbol(symbols, h.location_name as i64, "MAPSEC_")
    );
    println!("Area Icon:       {}", h.area_icon);
    println!(
        "Weather:         {}",
        resolve_map_symbol(symbols, h.weather_id as i64, "OVERWORLD_WEATHER_")
    );
    println!(
        "Camera:          {}",
        resolve_map_symbol(symbols, h.camera_angle_id as i64, "CAMERA_TYPE_")
    );
    println!("Worldmap:        ({}, {})", h.worldmap_x, h.worldmap_y);
    println!("Kanto:           {}", h.kanto_flag);
    println!(
        "Battle BG:       {}",
        resolve_map_symbol(symbols, h.battle_background as i64, "BATTLE_BG_")
    );
    println!("Flags:           0x{:02X}", h.flags);
}

pub fn print_event_file(event: &JsonEventFile, id: u32) {
    println!("Event File {}", id);
    println!("============");
    println!("BG Events:      {}", event.bg_events.len());
    println!("Object Events:  {}", event.object_events.len());
    println!("Warp Events:    {}", event.warp_events.len());
    println!("Coord Events:   {}", event.coord_events.len());

    if !event.bg_events.is_empty() {
        println!("\nBackground Events");
        for (i, bg) in event.bg_events.iter().enumerate() {
            println!(
                "  #{i}: type={} script={} pos=({}, {}, {}) facing={}",
                bg.event_type,
                bg.script,
                bg.x,
                bg.y,
                bg.z,
                bg.player_facing_dir.as_deref().unwrap_or("None")
            );
        }
    }

    if !event.object_events.is_empty() {
        println!("\nObject Events");
        for (i, obj) in event.object_events.iter().enumerate() {
            println!(
                "  #{i}: {} gfx={} script={} pos=({}, {}, {})",
                obj.id, obj.graphics_id, obj.script, obj.x, obj.y, obj.z
            );
        }
    }

    if !event.warp_events.is_empty() {
        println!("\nWarp Events");
        for (i, warp) in event.warp_events.iter().enumerate() {
            println!(
                "  #{i}: pos=({}, {}) -> {}:{}",
                warp.x, warp.z, warp.dest_header_id, warp.dest_warp_id
            );
        }
    }

    if !event.coord_events.is_empty() {
        println!("\nCoord Events");
        for (i, coord) in event.coord_events.iter().enumerate() {
            println!(
                "  #{i}: script={} area=({}, {}) {}x{} y={} var={} value={}",
                coord.script,
                coord.x,
                coord.z,
                coord.width,
                coord.length,
                coord.y,
                coord.var.as_deref().unwrap_or("None"),
                coord.value
            );
        }
    }
}

pub fn print_encounter_file(encounter: &JsonEncounterFile, id: u32, family: GameFamily) {
    let join_or_none = |values: &[String]| {
        if values.is_empty() {
            "None".to_string()
        } else {
            values.join(", ")
        }
    };

    println!("Encounter File {} ({family:?})", id);
    println!("=======================");
    println!("Land Rate:      {}", encounter.land_rate);
    println!("Surf Rate:      {}", encounter.surf_rate);
    println!("Old Rod Rate:   {}", encounter.old_rod_rate);
    println!("Good Rod Rate:  {}", encounter.good_rod_rate);
    println!("Super Rod Rate: {}", encounter.super_rod_rate);
    println!(
        "Form Rates:     [{}, {}, {}, {}, {}]",
        encounter.rate_form0,
        encounter.rate_form1,
        encounter.rate_form2,
        encounter.rate_form3,
        encounter.rate_form4
    );
    println!("Unown Table:    {}", encounter.unown_table);

    if !encounter.land_encounters.is_empty() {
        println!("\nLand Encounters");
        for (i, e) in encounter.land_encounters.iter().enumerate() {
            println!("  #{i}: {} @ lv{}", e.species, e.level);
        }
    }

    println!("\nSpecial Slots");
    println!("  Swarms:    {}", join_or_none(&encounter.swarms));
    println!("  Day:       {}", join_or_none(&encounter.day));
    println!("  Night:     {}", join_or_none(&encounter.night));
    println!("  Radar:     {}", join_or_none(&encounter.radar));
    println!("  Ruby:      {}", join_or_none(&encounter.ruby));
    println!("  Sapphire:  {}", join_or_none(&encounter.sapphire));
    println!("  Emerald:   {}", join_or_none(&encounter.emerald));
    println!("  FireRed:   {}", join_or_none(&encounter.firered));
    println!("  LeafGreen: {}", join_or_none(&encounter.leafgreen));

    let print_water = |label: &str, entries: &[WaterEncounterEntryJson]| {
        if entries.is_empty() {
            println!("\n{label}: None");
            return;
        }
        println!("\n{label}");
        for (i, e) in entries.iter().enumerate() {
            println!("  #{i}: {} @ lv{}-{}", e.species, e.level_min, e.level_max);
        }
    };

    print_water("Surf Encounters", &encounter.surf_encounters);
    print_water("Old Rod Encounters", &encounter.old_rod_encounters);
    print_water("Good Rod Encounters", &encounter.good_rod_encounters);
    print_water("Super Rod Encounters", &encounter.super_rod_encounters);

    if family == GameFamily::HGSS {
        println!(
            "\nRock Smash Rate: {}",
            encounter.rock_smash_rate.unwrap_or_default()
        );
        if let Some(rock_smash) = &encounter.rock_smash_encounters {
            print_water("Rock Smash Encounters", rock_smash);
        }

        if let Some(morning) = &encounter.morning {
            if morning.is_empty() {
                println!("\nMorning Encounters: None");
            } else {
                println!("\nMorning Encounters");
                for (i, e) in morning.iter().enumerate() {
                    println!("  #{i}: {} @ lv{}", e.species, e.level);
                }
            }
        }
    }
}

pub fn print_map_header(header: &MapHeader, id: u16, symbols: &SymbolTable, ws: &Workspace) {
    let (game_name, divider_len) = match header {
        MapHeader::DP(_) => ("Diamond/Pearl", 29),
        MapHeader::Pt(_) => ("Platinum", 25),
        MapHeader::HGSS(_) => ("HeartGold/SoulSilver", 36),
    };

    print_map_header_intro(header, id, ws, game_name, divider_len);

    match header {
        MapHeader::DP(h) => print_map_header_dp(h, symbols),
        MapHeader::Pt(h) => print_map_header_pt(h, symbols),
        MapHeader::HGSS(h) => print_map_header_hgss(h, symbols),
    }
}