added ability info

master
hheik 2023-04-01 19:28:19 +03:00
parent b071b55a58
commit a8fd4e8ba1
8 changed files with 1268 additions and 128 deletions

1066
data/abilities.json Normal file

File diff suppressed because it is too large Load Diff

View File

@ -7,10 +7,7 @@ use std::{
use bevy::prelude::*; use bevy::prelude::*;
use json::JsonValue; use json::JsonValue;
use crate::{ use crate::{json::*, pokemon::*};
json::{read_characteristics, read_natures, read_pokedex},
pokemon::*,
};
pub struct DatabasePlugin; pub struct DatabasePlugin;
@ -19,6 +16,7 @@ impl Plugin for DatabasePlugin {
app.insert_resource(Database::<Pokemon>::default()) app.insert_resource(Database::<Pokemon>::default())
.insert_resource(Database::<Nature>::default()) .insert_resource(Database::<Nature>::default())
.insert_resource(Database::<Characteristic>::default()) .insert_resource(Database::<Characteristic>::default())
.insert_resource(Database::<Ability>::default())
.add_startup_system(database_setup); .add_startup_system(database_setup);
} }
} }
@ -46,9 +44,11 @@ where
fn database_setup( fn database_setup(
mut pokemon_database: ResMut<Database<Pokemon>>, mut pokemon_database: ResMut<Database<Pokemon>>,
mut ability_database: ResMut<Database<Ability>>,
mut nature_database: ResMut<Database<Nature>>, mut nature_database: ResMut<Database<Nature>>,
mut characteristic_database: ResMut<Database<Characteristic>>, mut characteristic_database: ResMut<Database<Characteristic>>,
) { ) {
ability_database.populate_from_json(&read_abilities());
nature_database.populate_from_json(&read_natures()); nature_database.populate_from_json(&read_natures());
pokemon_database.populate_from_json(&read_pokedex()); pokemon_database.populate_from_json(&read_pokedex());
characteristic_database.populate_from_json(&read_characteristics()); characteristic_database.populate_from_json(&read_characteristics());

View File

@ -1,4 +1,4 @@
use super::inspector::{Battle, PokemonInstance}; use super::inspector::{Battle, ParsedPokemonInstance};
use crate::pokemon::*; use crate::pokemon::*;
const PLAYER_PARTY_OFFSET: usize = 0x21E42C; const PLAYER_PARTY_OFFSET: usize = 0x21E42C;
@ -29,7 +29,7 @@ struct Header {
checksum: u16, checksum: u16,
} }
pub fn parse_battle_party(data: &Vec<u8>) -> Result<Battle, String> { pub fn parse_battle_party(data: &Vec<u8>) -> Result<Battle<ParsedPokemonInstance>, String> {
let mut battle = Battle::default(); let mut battle = Battle::default();
for i in 0..PARTY_MAX_SIZE { for i in 0..PARTY_MAX_SIZE {
if let Some(instance) = try_parse(data, PLAYER_PARTY_OFFSET + i * POKEMON_SIZE) { if let Some(instance) = try_parse(data, PLAYER_PARTY_OFFSET + i * POKEMON_SIZE) {
@ -42,13 +42,13 @@ pub fn parse_battle_party(data: &Vec<u8>) -> Result<Battle, String> {
Ok(battle) Ok(battle)
} }
fn try_parse(data: &[u8], offset: usize) -> Option<PokemonInstance> { fn try_parse(data: &[u8], offset: usize) -> Option<ParsedPokemonInstance> {
let encrypted = &data[offset..offset + POKEMON_SIZE]; let encrypted = &data[offset..offset + POKEMON_SIZE];
if !contains_pokemon(encrypted) { if !contains_pokemon(encrypted) {
return None; return None;
} }
let decrypted = decrypt_pokemon(encrypted).ok()?; let decrypted = decrypt_pokemon(encrypted).ok()?;
// println!("{}", debug_pokemon_data(&decrypted)); println!("{}", debug_pokemon_data(&decrypted));
let pokemon = parse_pokemon(&decrypted).ok()?; let pokemon = parse_pokemon(&decrypted).ok()?;
Some(pokemon) Some(pokemon)
} }
@ -113,40 +113,18 @@ fn decrypt_pokemon(encrypted: &[u8]) -> Result<Vec<u8>, String> {
Ok(unshuffled) Ok(unshuffled)
} }
fn parse_pokemon(data: &[u8]) -> Result<PokemonInstance, String> { fn parse_pokemon(data: &[u8]) -> Result<ParsedPokemonInstance, String> {
validate_size(data)?; validate_size(data)?;
let mut instance = PokemonInstance::default(); let mut instance = ParsedPokemonInstance::default();
let offset_a: usize = 0x08 + 0 * BLOCK_SIZE; let offset_a: usize = 0x08 + 0 * BLOCK_SIZE;
let offset_b: usize = 0x08 + 1 * BLOCK_SIZE; let offset_b: usize = 0x08 + 1 * BLOCK_SIZE;
let offset_c: usize = 0x08 + 2 * BLOCK_SIZE; let offset_c: usize = 0x08 + 2 * BLOCK_SIZE;
let offset_d: usize = 0x08 + 3 * BLOCK_SIZE; let offset_d: usize = 0x08 + 3 * BLOCK_SIZE;
instance.pokemon.national = read_short(data, offset_a + 0x00); instance.pokemon = read_short(data, offset_a + 0x00);
instance.ability = read_byte(data, offset_a + 0x0D);
let ivs = read_long(data, offset_b + 0x10);
instance.ivs.insert(BaseStat::Hp, ((ivs >> 0) & 31) as u8);
instance
.ivs
.insert(BaseStat::Attack, ((ivs >> 5) & 31) as u8);
instance
.ivs
.insert(BaseStat::Defense, ((ivs >> 10) & 31) as u8);
instance
.ivs
.insert(BaseStat::Speed, ((ivs >> 15) & 31) as u8);
instance
.ivs
.insert(BaseStat::SpecialAttack, ((ivs >> 20) & 31) as u8);
instance
.ivs
.insert(BaseStat::SpecialDefense, ((ivs >> 25) & 31) as u8);
instance.nature = Some(Nature {
pid: read_byte(data, offset_b + 0x19),
..Default::default()
});
instance instance
.evs .evs
@ -167,6 +145,26 @@ fn parse_pokemon(data: &[u8]) -> Result<PokemonInstance, String> {
.evs .evs
.insert(BaseStat::SpecialDefense, read_byte(data, offset_a + 0x15)); .insert(BaseStat::SpecialDefense, read_byte(data, offset_a + 0x15));
let ivs = read_long(data, offset_b + 0x10);
instance.ivs.insert(BaseStat::Hp, ((ivs >> 0) & 31) as u8);
instance
.ivs
.insert(BaseStat::Attack, ((ivs >> 5) & 31) as u8);
instance
.ivs
.insert(BaseStat::Defense, ((ivs >> 10) & 31) as u8);
instance
.ivs
.insert(BaseStat::Speed, ((ivs >> 15) & 31) as u8);
instance
.ivs
.insert(BaseStat::SpecialAttack, ((ivs >> 20) & 31) as u8);
instance
.ivs
.insert(BaseStat::SpecialDefense, ((ivs >> 25) & 31) as u8);
instance.nature = read_byte(data, offset_b + 0x19);
Ok(instance) Ok(instance)
} }

View File

@ -3,7 +3,10 @@ use std::collections::HashMap;
use bevy::prelude::*; use bevy::prelude::*;
use bevy_egui::EguiContexts; use bevy_egui::EguiContexts;
use crate::{database::load_url_asset, pokemon::*}; use crate::{
database::{load_url_asset, Database},
pokemon::*,
};
use super::ui::{EguiAsset, UiAssets}; use super::ui::{EguiAsset, UiAssets};
@ -18,23 +21,82 @@ impl Plugin for InspectorPlugin {
#[derive(Resource, Default)] #[derive(Resource, Default)]
pub struct Inspector { pub struct Inspector {
pub battle: Battle, pub battle: Battle<PokemonInstance>,
} }
#[derive(Default, Clone)] #[derive(Default, Clone)]
pub struct Battle { pub struct Battle<T> {
pub player_party: Vec<PokemonInstance>, pub player_party: Vec<T>,
pub enemy_party: Vec<PokemonInstance>, pub enemy_party: Vec<T>,
} }
#[derive(Default, Clone)] #[derive(Default, Clone)]
pub struct PokemonInstance { pub struct PokemonInstance {
pub pokemon: Pokemon, pub pokemon: Pokemon,
pub ability: Option<Ability>,
pub nature: Option<Nature>, pub nature: Option<Nature>,
pub ivs: HashMap<BaseStat, u8>, pub ivs: HashMap<BaseStat, u8>,
pub evs: HashMap<BaseStat, u8>, pub evs: HashMap<BaseStat, u8>,
} }
impl PokemonInstance {
pub fn from_parsed(
parsed: &ParsedPokemonInstance,
pokedex: &Res<Database<Pokemon>>,
natures: &Res<Database<Nature>>,
abilities: &Res<Database<Ability>>,
) -> Result<Self, String> {
let mut instance = PokemonInstance::default();
// pokemon data
if let Some(pokemon) = pokedex
.map
.values()
.find(|pokemon| pokemon.national == parsed.pokemon)
{
instance.pokemon = pokemon.clone();
} else {
return Err(format!("National dex not found: {}", parsed.pokemon));
}
// ability data
if let Some(ability) = abilities
.map
.values()
.find(|ability| ability.id == parsed.ability as u16)
{
instance.ability = Some(ability.clone());
} else {
return Err(format!("Ability id not found: {}", parsed.ability));
}
// nature data
if let Some(nature) = natures
.map
.values()
.find(|nature| nature.pid == parsed.nature)
{
instance.nature = Some(nature.clone());
} else {
return Err(format!("Nature pid not found: {}", parsed.nature));
}
instance.ivs = parsed.ivs.clone();
instance.evs = parsed.evs.clone();
Ok(instance)
}
}
#[derive(Default, Clone)]
pub struct ParsedPokemonInstance {
pub pokemon: u16,
pub ability: u8,
pub nature: u8,
pub ivs: HashMap<BaseStat, u8>,
pub evs: HashMap<BaseStat, u8>,
}
fn state_changed( fn state_changed(
inspector: Res<Inspector>, inspector: Res<Inspector>,
assets: Res<AssetServer>, assets: Res<AssetServer>,

View File

@ -9,16 +9,13 @@ use std::{
use crate::{database::Database, pokemon::*}; use crate::{database::Database, pokemon::*};
use super::{ use super::{battle::parse_battle_party, inspector::*};
battle::parse_battle_party,
inspector::{Battle, Inspector},
};
pub struct MemoryReaderPlugin; pub struct MemoryReaderPlugin;
impl Plugin for MemoryReaderPlugin { impl Plugin for MemoryReaderPlugin {
fn build(&self, app: &mut App) { fn build(&self, app: &mut App) {
app.add_event::<ParsedBattleEvent>() app.add_event::<BattleEvent>()
.insert_resource(EventQueue::default()) .insert_resource(EventQueue::default())
.add_startup_system(init_memory_reader) .add_startup_system(init_memory_reader)
.add_system(flush_updates) .add_system(flush_updates)
@ -27,54 +24,40 @@ impl Plugin for MemoryReaderPlugin {
} }
#[derive(Default)] #[derive(Default)]
pub struct ParsedBattleEvent(Battle); pub struct BattleEvent(Battle<PokemonInstance>);
#[derive(Resource, Default)] #[derive(Resource, Default)]
struct EventQueue { struct EventQueue {
updates: Arc<Mutex<Vec<ParsedBattleEvent>>>, updates: Arc<Mutex<Vec<Battle<ParsedPokemonInstance>>>>,
} }
fn flush_updates( fn flush_updates(
queue: Res<EventQueue>, queue: Res<EventQueue>,
pokedex: Res<Database<Pokemon>>, pokedex: Res<Database<Pokemon>>,
abilities: Res<Database<Ability>>,
natures: Res<Database<Nature>>, natures: Res<Database<Nature>>,
mut events: EventWriter<ParsedBattleEvent>, mut events: EventWriter<BattleEvent>,
) { ) {
match queue.updates.lock() { match queue.updates.lock() {
Ok(mut updates) => { Ok(mut updates) => {
for mut update in updates.drain(..) { for update in updates.drain(..) {
for mut instance in update let mut battle = Battle::default();
.0
.player_party
.iter_mut()
.chain(update.0.enemy_party.iter_mut())
{
// Fill pokemon data
if let Some(pokemon) = pokedex
.map
.values()
.find(|pokemon| pokemon.national == instance.pokemon.national)
{
instance.pokemon = pokemon.clone();
} else {
warn!("National dex not found: {}", instance.pokemon.national);
}
// Fill nature data for parsed in update.player_party.iter() {
if let Some(instance_nature) = instance.nature.clone() { match PokemonInstance::from_parsed(parsed, &pokedex, &natures, &abilities) {
if let Some(nature) = natures Ok(instance) => battle.player_party.push(instance),
.map Err(err) => warn!("{err}"),
.values()
.find(|nature| nature.pid == instance_nature.pid)
{
instance.nature = Some(nature.clone());
} else {
warn!("Nature pid not found: {}", instance_nature.pid);
}
} }
} }
events.send(update) for parsed in update.enemy_party.iter() {
match PokemonInstance::from_parsed(parsed, &pokedex, &natures, &abilities) {
Ok(instance) => battle.enemy_party.push(instance),
Err(err) => warn!("{err}"),
}
}
events.send(BattleEvent(battle))
} }
} }
Err(err) => { Err(err) => {
@ -83,7 +66,7 @@ fn flush_updates(
} }
} }
fn update_inspector(mut inspector: ResMut<Inspector>, mut events: EventReader<ParsedBattleEvent>) { fn update_inspector(mut inspector: ResMut<Inspector>, mut events: EventReader<BattleEvent>) {
for event in events.iter() { for event in events.iter() {
inspector.battle = event.0.clone(); inspector.battle = event.0.clone();
} }
@ -113,7 +96,7 @@ fn init_memory_reader(queue: Res<EventQueue>) {
match fs::read(&event.path) { match fs::read(&event.path) {
Ok(data) => match parse_battle_party(&data) { Ok(data) => match parse_battle_party(&data) {
Ok(battle) => match updates.lock() { Ok(battle) => match updates.lock() {
Ok(mut updates) => updates.push(ParsedBattleEvent(battle)), Ok(mut updates) => updates.push(battle),
Err(err) => error!("{err}"), Err(err) => error!("{err}"),
}, },
Err(err) => error!("Failed to parse party data: ${err}"), Err(err) => error!("Failed to parse party data: ${err}"),

View File

@ -9,7 +9,7 @@ use super::inspector::{Inspector, PokemonInstance};
const BAR_HEIGHT: f32 = 12.0; const BAR_HEIGHT: f32 = 12.0;
const SPRITE_SIZE: f32 = 128.0; const SPRITE_SIZE: f32 = 128.0;
const SPRITE_PADDING: f32 = 32.0; const SPRITE_PADDING: f32 = 16.0;
const BASE_STAT_WIDTH_MULT: f32 = 0.5; const BASE_STAT_WIDTH_MULT: f32 = 0.5;
const IV_WIDTH_MULT: f32 = 4.0; const IV_WIDTH_MULT: f32 = 4.0;
const EV_WIDTH_MULT: f32 = 0.25; const EV_WIDTH_MULT: f32 = 0.25;
@ -106,9 +106,11 @@ fn render_pokemon_instance(
) { ) {
let sprite_size = egui::Vec2::new(SPRITE_SIZE, SPRITE_SIZE); let sprite_size = egui::Vec2::new(SPRITE_SIZE, SPRITE_SIZE);
let pokemon = instance.pokemon.clone(); let pokemon = instance.pokemon.clone();
let ability = instance.ability.clone();
let nature = instance.nature.clone(); let nature = instance.nature.clone();
ui.horizontal(|ui| { ui.horizontal(|ui| {
ui.vertical(|ui| {
ui.horizontal(|ui| { ui.horizontal(|ui| {
ui.set_min_width(sprite_size.x + SPRITE_PADDING); ui.set_min_width(sprite_size.x + SPRITE_PADDING);
ui.set_max_width(sprite_size.x + SPRITE_PADDING); ui.set_max_width(sprite_size.x + SPRITE_PADDING);
@ -123,6 +125,24 @@ fn render_pokemon_instance(
)); ));
}); });
ui.label(ability.as_ref().map_or("-", |a| a.name.as_str()));
// Sprite
ui.horizontal(|ui| {
ui.set_min_size(sprite_size);
ui.set_max_size(sprite_size);
if let Some(EguiAsset(_, rendered_texture_id)) =
ui_assets.sprite_map.get(&pokemon.key())
{
ui.add(egui::Image::new(*rendered_texture_id, sprite_size));
}
});
});
ui.add_space(SPRITE_PADDING);
ui.vertical(|ui| {
ui.add_space(16.);
// Headings // Headings
ui.horizontal(|ui| { ui.horizontal(|ui| {
for (i, width) in COLUMNS.iter().enumerate() { for (i, width) in COLUMNS.iter().enumerate() {
@ -149,23 +169,6 @@ fn render_pokemon_instance(
}); });
} }
}); });
});
ui.horizontal(|ui| {
// Sprite
ui.horizontal(|ui| {
ui.set_min_size(sprite_size);
ui.set_max_size(sprite_size);
if let Some(EguiAsset(_, rendered_texture_id)) =
ui_assets.sprite_map.get(&pokemon.key())
{
ui.add(egui::Image::new(*rendered_texture_id, sprite_size));
}
});
ui.add_space(SPRITE_PADDING);
ui.vertical(|ui| {
for stat in BaseStat::all() { for stat in BaseStat::all() {
ui.horizontal(|ui| { ui.horizontal(|ui| {
let mut column_size = COLUMNS.iter(); let mut column_size = COLUMNS.iter();

View File

@ -26,6 +26,10 @@ pub fn parse_json_file<P: AsRef<Path>>(path: P) -> JsonValue {
json::parse(fs::read_to_string(path).unwrap().as_ref()).unwrap() json::parse(fs::read_to_string(path).unwrap().as_ref()).unwrap()
} }
pub fn read_abilities() -> JsonValue {
parse_json_file(DATA_DIR.join("abilities.json"))
}
pub fn read_natures() -> JsonValue { pub fn read_natures() -> JsonValue {
parse_json_file(DATA_DIR.join("natures.json")) parse_json_file(DATA_DIR.join("natures.json"))
} }

View File

@ -247,6 +247,30 @@ impl GetKey for Type {
} }
} }
#[derive(Default, Debug, Clone)]
pub struct Ability {
pub id: u16,
pub name: String,
}
impl FromJson for Ability {
fn from_json(json: &json::JsonValue) -> Option<Self>
where
Self: Sized,
{
Some(Self {
id: json["id"].as_u16()?,
name: json["name"].as_str()?.to_string(),
})
}
}
impl GetKey for Ability {
fn key(&self) -> String {
self.name.clone()
}
}
#[derive(Default, Debug, Clone, Reflect, FromReflect, PartialEq)] #[derive(Default, Debug, Clone, Reflect, FromReflect, PartialEq)]
pub struct Nature { pub struct Nature {
pub id: u8, pub id: u8,