started implementing iv-peeker

master
hheik 2023-03-30 23:11:36 +03:00
parent 920cff3bbe
commit 635e67d6c7
16 changed files with 359 additions and 92 deletions

2
Cargo.lock generated
View File

@ -3286,7 +3286,7 @@ dependencies = [
]
[[package]]
name = "smart-iv-calculator"
name = "smart_iv_calculator"
version = "0.1.0"
dependencies = [
"bevy",

View File

@ -1,5 +1,5 @@
[package]
name = "smart-iv-calculator"
name = "smart_iv_calculator"
version = "0.1.0"
edition = "2021"

View File

@ -2,6 +2,7 @@
"name": "Hardy",
"alias": "hardy",
"id": 1,
"pid": 0,
"increased": null,
"decreased": null
},
@ -9,6 +10,7 @@
"name": "Bold",
"alias": "bold",
"id": 2,
"pid": 5,
"increased": "defense",
"decreased": "attack"
},
@ -16,6 +18,7 @@
"name": "Modest",
"alias": "modest",
"id": 3,
"pid": 15,
"increased": "special_attack",
"decreased": "attack"
},
@ -23,6 +26,7 @@
"name": "Calm",
"alias": "calm",
"id": 4,
"pid": 20,
"increased": "special_defense",
"decreased": "attack"
},
@ -30,6 +34,7 @@
"name": "Timid",
"alias": "timid",
"id": 5,
"pid": 10,
"increased": "speed",
"decreased": "attack"
},
@ -37,6 +42,7 @@
"name": "Lonely",
"alias": "lonely",
"id": 6,
"pid": 1,
"increased": "attack",
"decreased": "defense"
},
@ -44,6 +50,7 @@
"name": "Docile",
"alias": "docile",
"id": 7,
"pid": 6,
"increased": null,
"decreased": null
},
@ -51,6 +58,7 @@
"name": "Mild",
"alias": "mild",
"id": 8,
"pid": 16,
"increased": "special_attack",
"decreased": "defense"
},
@ -58,6 +66,7 @@
"name": "Gentle",
"alias": "gentle",
"id": 9,
"pid": 21,
"increased": "special_defense",
"decreased": "defense"
},
@ -65,6 +74,7 @@
"name": "Hasty",
"alias": "hasty",
"id": 10,
"pid": 11,
"increased": "speed",
"decreased": "defense"
},
@ -72,6 +82,7 @@
"name": "Adamant",
"alias": "adamant",
"id": 11,
"pid": 3,
"increased": "attack",
"decreased": "special_attack"
},
@ -79,6 +90,7 @@
"name": "Impish",
"alias": "impish",
"id": 12,
"pid": 8,
"increased": "defense",
"decreased": "special_attack"
},
@ -86,6 +98,7 @@
"name": "Bashful",
"alias": "bashful",
"id": 13,
"pid": 18,
"increased": null,
"decreased": null
},
@ -93,6 +106,7 @@
"name": "Careful",
"alias": "careful",
"id": 14,
"pid": 23,
"increased": "special_defense",
"decreased": "special_attack"
},
@ -100,6 +114,7 @@
"name": "Rash",
"alias": "rash",
"id": 15,
"pid": 19,
"increased": "special_attack",
"decreased": "special_defense"
},
@ -107,6 +122,7 @@
"name": "Jolly",
"alias": "jolly",
"id": 16,
"pid": 13,
"increased": "speed",
"decreased": "special_attack"
},
@ -114,6 +130,7 @@
"name": "Naughty",
"alias": "naughty",
"id": 17,
"pid": 4,
"increased": "attack",
"decreased": "special_defense"
},
@ -121,6 +138,7 @@
"name": "Lax",
"alias": "lax",
"id": 18,
"pid": 9,
"increased": "defense",
"decreased": "special_defense"
},
@ -128,6 +146,7 @@
"name": "Quirky",
"alias": "quirky",
"id": 19,
"pid": 24,
"increased": null,
"decreased": null
},
@ -135,6 +154,7 @@
"name": "Naive",
"alias": "naive",
"id": 20,
"pid": 14,
"increased": "speed",
"decreased": "special_defense"
},
@ -142,6 +162,7 @@
"name": "Brave",
"alias": "brave",
"id": 21,
"pid": 2,
"increased": "attack",
"decreased": "speed"
},
@ -149,6 +170,7 @@
"name": "Relaxed",
"alias": "relaxed",
"id": 22,
"pid": 7,
"increased": "defense",
"decreased": "speed"
},
@ -156,6 +178,7 @@
"name": "Quiet",
"alias": "quiet",
"id": 23,
"pid": 17,
"increased": "special_attack",
"decreased": "speed"
},
@ -163,6 +186,7 @@
"name": "Sassy",
"alias": "sassy",
"id": 24,
"pid": 22,
"increased": "special_defense",
"decreased": "speed"
},
@ -170,6 +194,7 @@
"name": "Serious",
"alias": "serious",
"id": 25,
"pid": 12,
"increased": null,
"decreased": null
}

5
src/bin/ivcalc.rs Normal file
View File

@ -0,0 +1,5 @@
use smart_iv_calculator::ivcalc;
fn main() {
ivcalc::run()
}

5
src/bin/ivpeek.rs Normal file
View File

@ -0,0 +1,5 @@
use smart_iv_calculator::ivpeek;
fn main() {
ivpeek::run()
}

View File

@ -1,28 +1,13 @@
use std::collections::HashMap;
use bevy::prelude::*;
use json::JsonValue;
use lazy_static::lazy_static;
use std::{
collections::HashMap,
fs,
path::{Path, PathBuf},
use crate::{
json::{read_characteristics, read_natures, read_pokedex},
pokemon::*,
};
use crate::pokemon::*;
lazy_static! {
static ref DATA_DIR: PathBuf = "./data".into();
pub static ref TYPE_MAP: HashMap<u8, Type> = {
let types = parse_json_file(DATA_DIR.join("types.json"));
let mut map = HashMap::new();
for type_data in types.members() {
if let Some(type_data) = Type::from_json(type_data) {
map.insert(type_data.id, type_data);
}
}
map
};
}
pub struct DatabasePlugin;
impl Plugin for DatabasePlugin {
@ -60,28 +45,7 @@ fn database_setup(
mut nature_database: ResMut<Database<Nature>>,
mut characteristic_database: ResMut<Database<Characteristic>>,
) {
let natures = parse_json_file(DATA_DIR.join("natures.json"));
nature_database.populate_from_json(&natures);
let pokedex = parse_json_file(DATA_DIR.join("pokedex.json"));
pokemon_database.populate_from_json(&pokedex);
let characteristics = parse_json_file(DATA_DIR.join("characteristics.json"));
characteristic_database.populate_from_json(&characteristics);
}
pub fn parse_json_file<P: AsRef<Path>>(path: P) -> JsonValue {
json::parse(fs::read_to_string(path).unwrap().as_ref()).unwrap()
}
pub fn load_url_asset(
url: String,
path: PathBuf,
assets: &Res<AssetServer>,
) -> Result<Handle<Image>, Box<dyn std::error::Error>> {
let system_path = Path::new("assets").join(&path);
if Path::exists(&path) {
return Ok(assets.load(path));
}
let data = reqwest::blocking::get(&url)?.bytes()?;
fs::write(&system_path, data).unwrap();
Ok(assets.load(path))
nature_database.populate_from_json(&read_natures());
pokemon_database.populate_from_json(&read_pokedex());
characteristic_database.populate_from_json(&read_characteristics());
}

View File

@ -1,12 +1,10 @@
use bevy::{prelude::*, window::WindowResolution};
pub mod database;
pub mod inspector;
pub mod pokemon;
pub mod server;
pub mod ui;
mod calculator;
mod server;
mod ui;
fn main() {
pub fn run() {
App::new()
.add_plugins(
DefaultPlugins
@ -21,8 +19,8 @@ fn main() {
})
.set(ImagePlugin::default_nearest()),
)
.add_plugin(database::DatabasePlugin)
.add_plugin(inspector::InspectorPlugin)
.add_plugin(crate::database::DatabasePlugin)
.add_plugin(calculator::CalculatorPlugin)
.add_plugin(ui::UiPlugin)
.add_plugin(server::ServerPlugin)
.run();

View File

@ -1,19 +1,25 @@
use std::{
fs,
path::{Path, PathBuf},
};
use bevy::prelude::*;
use bevy_egui::EguiContexts;
use crate::{
database::{load_url_asset, Database},
pokemon::*,
use crate::database::*;
use crate::pokemon::*;
use super::{
server::Request,
ui::{UiAssets, UiState},
};
pub struct InspectorPlugin;
pub struct CalculatorPlugin;
impl Plugin for InspectorPlugin {
impl Plugin for CalculatorPlugin {
fn build(&self, app: &mut App) {
app.register_type::<Inspector>()
.insert_resource(Inspector::default())
app.register_type::<Calculator>()
.insert_resource(Calculator::default())
.add_system(state_changed)
.add_system(request_handler.in_base_set(CoreSet::PreUpdate));
}
@ -21,7 +27,7 @@ impl Plugin for InspectorPlugin {
#[derive(Reflect, Resource, Default, Debug, Clone)]
#[reflect(Resource)]
pub struct Inspector {
pub struct Calculator {
pub pokemon: Option<Pokemon>,
pub nature: Option<Nature>,
pub characteristic: Option<Characteristic>,
@ -29,15 +35,15 @@ pub struct Inspector {
}
fn state_changed(
inspector: Res<Inspector>,
calculator: Res<Calculator>,
assets: Res<AssetServer>,
mut ui_assets: ResMut<UiAssets>,
mut contexts: EguiContexts,
) {
if let (Some(pokemon), true) = (inspector.pokemon.as_ref(), inspector.is_changed()) {
if let (Some(pokemon), true) = (calculator.pokemon.as_ref(), calculator.is_changed()) {
if !ui_assets.sprite_map.contains_key(&pokemon.key()) {
let handle = load_url_asset(
pokemon.get_url(),
pokemon.sprite_url(),
format!("cache/{name}.png", name = pokemon.key()).into(),
&assets,
);
@ -53,7 +59,7 @@ fn state_changed(
fn request_handler(
mut request_events: EventReader<Request>,
mut inspector: ResMut<Inspector>,
mut calculator: ResMut<Calculator>,
mut ui: ResMut<UiState>,
characteristics: Res<Database<Characteristic>>,
natures: Res<Database<Nature>>,
@ -62,10 +68,10 @@ fn request_handler(
info!("{:#}", request);
match request.urn.as_str() {
"clear" => {
inspector.pokemon = None;
inspector.nature = None;
inspector.characteristic = None;
inspector.derived_stats = DerivedStats::default();
calculator.pokemon = None;
calculator.nature = None;
calculator.characteristic = None;
calculator.derived_stats = DerivedStats::default();
ui.name = String::from("");
ui.level = String::from("");
ui.derived_stats.clear();
@ -77,11 +83,11 @@ fn request_handler(
}
if let Some(level) = data["level"].as_i32() {
ui.level = level.to_string();
inspector.derived_stats.level = Some(level);
calculator.derived_stats.level = Some(level);
}
if let Some(nature) = data["nature"].as_str() {
if let Some(nature) = natures.map.get(&nature.to_lowercase()).cloned() {
inspector.nature = Some(nature);
calculator.nature = Some(nature);
}
}
if let Some(characteristic) = data["characteristic"].as_str() {
@ -90,13 +96,13 @@ fn request_handler(
.get(&characteristic.to_lowercase())
.cloned()
{
inspector.characteristic = Some(characteristic);
calculator.characteristic = Some(characteristic);
}
}
for stat in BaseStat::all() {
if let Some(value) = data[stat.key()].as_i32() {
ui.derived_stats.insert(stat, value.to_string());
inspector.derived_stats.set_stat_value(stat, Some(value));
calculator.derived_stats.set_stat_value(stat, Some(value));
}
}
}
@ -106,3 +112,17 @@ fn request_handler(
}
}
}
pub fn load_url_asset(
url: String,
path: PathBuf,
assets: &Res<AssetServer>,
) -> Result<Handle<Image>, Box<dyn std::error::Error>> {
let system_path = Path::new("assets").join(&path);
if Path::exists(&path) {
return Ok(assets.load(path));
}
let data = reqwest::blocking::get(&url)?.bytes()?;
fs::write(&system_path, data).unwrap();
Ok(assets.load(path))
}

View File

@ -3,7 +3,10 @@ use bevy_egui::{egui, EguiContexts, EguiPlugin};
use lazy_static::lazy_static;
use std::collections::HashMap;
use crate::{database::Database, inspector::Inspector, pokemon::*};
use crate::database::Database;
use crate::pokemon::*;
use super::calculator::Calculator;
const INPUT_LABEL_WIDTH: f32 = 100.0;
const INPUT_TEXT_WIDTH: f32 = 150.0;
@ -69,18 +72,18 @@ fn load_assets(mut ui_assets: ResMut<UiAssets>, assets: Res<AssetServer>) {
fn handle_state(
ui_state: Res<UiState>,
pokemon: Res<Database<Pokemon>>,
mut inspector: ResMut<Inspector>,
mut calculator: ResMut<Calculator>,
) {
if ui_state.is_changed() {
if let Some(pokemon) = pokemon.map.get(ui_state.name.to_lowercase().as_str()) {
inspector.pokemon = Some(pokemon.clone());
calculator.pokemon = Some(pokemon.clone());
}
}
}
fn ui_system(
mut contexts: EguiContexts,
mut inspector: ResMut<Inspector>,
mut calculator: ResMut<Calculator>,
ui_assets: Res<UiAssets>,
mut ui_state: ResMut<UiState>,
mut rendered_texture_id: Local<egui::TextureId>,
@ -96,11 +99,11 @@ fn ui_system(
egui::CentralPanel::default().show(contexts.ctx_mut(), |ui| {
let sprite_size = egui::Vec2::new(128., 128.);
let pokemon = inspector.pokemon.clone();
let pokemon = calculator.pokemon.clone();
let pokemon = pokemon.as_ref();
let nature = inspector.nature.clone();
let nature = calculator.nature.clone();
let nature = nature.as_ref();
let characteristic = inspector.characteristic.clone();
let characteristic = calculator.characteristic.clone();
let characteristic = characteristic.as_ref();
// Pokemon
@ -128,11 +131,11 @@ fn ui_system(
egui::ComboBox::new("nature", "")
.selected_text(format!("{}", nature.map_or("-", |c| &c.name)))
.show_ui(ui, |ui| {
ui.selectable_value(&mut inspector.nature, None, "-");
ui.selectable_value(&mut calculator.nature, None, "-");
let mut chars: Vec<_> = natures.map.values().collect();
chars.sort_by_key(|c| c.name.clone());
for char in chars {
ui.selectable_value(&mut inspector.nature, Some(char.clone()), &char.name);
ui.selectable_value(&mut calculator.nature, Some(char.clone()), &char.name);
}
});
});
@ -147,12 +150,12 @@ fn ui_system(
egui::ComboBox::new("characteristic", "")
.selected_text(format!("{}", characteristic.map_or("-", |c| &c.name)))
.show_ui(ui, |ui| {
ui.selectable_value(&mut inspector.characteristic, None, "-");
ui.selectable_value(&mut calculator.characteristic, None, "-");
let mut chars: Vec<_> = characteristics.map.values().collect();
chars.sort_by_key(|c| c.name.clone());
for char in chars {
ui.selectable_value(
&mut inspector.characteristic,
&mut calculator.characteristic,
Some(char.clone()),
&char.name,
);
@ -212,7 +215,7 @@ fn ui_system(
if input.changed() {
*value = digit_filter(value);
if let Ok(value) = value.parse::<StatValue>() {
inspector.derived_stats.level = Some(value);
calculator.derived_stats.level = Some(value);
}
}
if input.gained_focus() {
@ -318,7 +321,7 @@ fn ui_system(
*value = digit_filter(value);
if let (Ok(value), derived) = (
value.parse::<StatValue>(),
inspector.derived_stats.value_mut(stat),
calculator.derived_stats.value_mut(stat),
) {
*derived = Some(value);
}
@ -330,8 +333,8 @@ fn ui_system(
let possible_ivs = if let (Some(pokemon), Some(level), Some(derived_stat)) = (
pokemon,
inspector.derived_stats.level,
inspector.derived_stats.value(stat),
calculator.derived_stats.level,
calculator.derived_stats.value(stat),
) {
calculate_possible_ivs(
stat,

24
src/ivpeek.rs Normal file
View File

@ -0,0 +1,24 @@
use bevy::{prelude::*, window::WindowResolution};
mod inspector;
mod ui;
pub fn run() {
App::new()
.add_plugins(
DefaultPlugins
.set(WindowPlugin {
primary_window: Some(Window {
resolution: WindowResolution::new(720., 540.),
resizable: false,
title: "Smart IV peeker".to_string(),
..default()
}),
..default()
})
.set(ImagePlugin::default_nearest()),
)
.add_plugin(crate::database::DatabasePlugin)
.add_plugin(ui::UiPlugin)
.run()
}

34
src/ivpeek/inspector.rs Normal file
View File

@ -0,0 +1,34 @@
use std::collections::HashMap;
use bevy::prelude::*;
use crate::pokemon::*;
pub struct InspectorPlugin;
impl Plugin for InspectorPlugin {
fn build(&self, app: &mut App) {
app.insert_resource(Inspector::default());
}
}
#[derive(Resource, Default)]
pub struct Inspector {
pub allies: Vec<PokemonInstance>,
pub enemies: Vec<PokemonInstance>,
}
impl Inspector {
pub fn clear(&mut self) {
self.allies.clear();
self.enemies.clear();
}
}
#[derive(Default)]
pub struct PokemonInstance {
pub pokemon: Pokemon,
pub nature: Option<Nature>,
pub ivs: HashMap<BaseStat, u8>,
pub evs: HashMap<BaseStat, u8>,
}

127
src/ivpeek/ui.rs Normal file
View File

@ -0,0 +1,127 @@
use bevy::prelude::*;
use bevy_egui::{egui, EguiContexts, EguiPlugin};
use lazy_static::lazy_static;
use std::collections::HashMap;
use crate::{database::Database, pokemon::*};
use super::inspector::{Inspector, PokemonInstance};
const BAR_HEIGHT: f32 = 12.0;
const BASE_STAT_WIDTH_MULT: f32 = 1.0;
const IV_WIDTH_MULT: f32 = 4.0;
const EV_WIDTH_MULT: f32 = 1.0;
lazy_static! {
static ref BASE_STAT_COLOR_RANGES: Vec<(u8, egui::Color32)> = vec![
(0, egui::Color32::from_rgb(255, 0, 0)),
(30, egui::Color32::from_rgb(255, 128, 0)),
(60, egui::Color32::from_rgb(255, 255, 0)),
(90, egui::Color32::from_rgb(128, 255, 0)),
(120, egui::Color32::from_rgb(0, 255, 0)),
(150, egui::Color32::from_rgb(0, 255, 128)),
(180, egui::Color32::from_rgb(0, 255, 255)),
];
static ref IV_COLOR_RANGES: Vec<(u8, egui::Color32)> = vec![
(0, egui::Color32::from_rgb(255, 0, 0)),
(16, egui::Color32::from_rgb(255, 128, 0)),
(22, egui::Color32::from_rgb(255, 255, 0)),
(27, egui::Color32::from_rgb(128, 255, 0)),
(30, egui::Color32::from_rgb(0, 255, 0)),
];
static ref COLUMNS: Vec<f32> = vec![
25.0, // Base stat
255.0 * BASE_STAT_WIDTH_MULT, // Base stat bar
60.0, // Stat name
40.0, // IV value
32.0 * IV_WIDTH_MULT, // IV bar
40.0, // EV value
255.0 * EV_WIDTH_MULT, // EV bar
];
}
pub struct UiPlugin;
impl Plugin for UiPlugin {
fn build(&self, app: &mut App) {
app.add_plugin(EguiPlugin)
.insert_resource(Inspector::default())
.insert_resource(UiAssets::default())
.add_startup_system(load_assets)
.add_system(ui_system)
.add_startup_system(test_init);
}
}
fn test_init(mut inspector: ResMut<Inspector>, pokemon_database: Res<Database<Pokemon>>) {
inspector.enemies.push(PokemonInstance {
pokemon: pokemon_database.map.get("baltoy").unwrap().clone(),
..default()
});
}
#[derive(Resource, Default)]
pub struct UiAssets {
pub bar_handle: Handle<Image>,
pub sprite_map: HashMap<String, (Handle<Image>, egui::TextureId)>,
}
fn load_assets(mut ui_assets: ResMut<UiAssets>, assets: Res<AssetServer>) {
ui_assets.bar_handle = assets.load("ui/bar.png");
}
fn ui_system(
mut contexts: EguiContexts,
inspector: Res<Inspector>,
ui_assets: Res<UiAssets>,
mut rendered_texture_id: Local<egui::TextureId>,
mut is_initialized: Local<bool>,
) {
if !*is_initialized {
*is_initialized = true;
*rendered_texture_id = contexts.add_image(ui_assets.bar_handle.clone_weak());
}
egui::CentralPanel::default().show(contexts.ctx_mut(), |ui| {
let sprite_size = egui::Vec2::new(128., 128.);
for instance in inspector.enemies.iter().chain(inspector.allies.iter()) {
let pokemon = instance.pokemon.clone();
// Sprite
ui.horizontal(|ui| {
ui.horizontal(|ui| {
ui.set_min_size(sprite_size);
if let Some((_, rendered_texture_id)) = ui_assets.sprite_map.get(&pokemon.key())
{
ui.add(egui::Image::new(*rendered_texture_id, sprite_size));
}
});
});
}
});
}
fn nature_color(stat: BaseStat, nature: Option<&Nature>) -> egui::Color32 {
if let Some(nature) = nature {
if nature.increased == Some(stat) {
egui::Color32::from_rgb(255, 64, 192)
} else if nature.decreased == Some(stat) {
egui::Color32::from_rgb(64, 192, 255)
} else {
egui::Color32::LIGHT_GRAY
}
} else {
egui::Color32::LIGHT_GRAY
}
}
fn iv_color(iv: u8) -> egui::Color32 {
for (treshold, color) in IV_COLOR_RANGES.iter().rev() {
if iv >= *treshold {
return color.clone();
}
}
IV_COLOR_RANGES
.first()
.map_or(egui::Color32::LIGHT_GRAY, |(_, color)| color.clone())
}

43
src/json.rs Normal file
View File

@ -0,0 +1,43 @@
use json::JsonValue;
use lazy_static::lazy_static;
use std::{
collections::HashMap,
fs,
path::{Path, PathBuf},
};
use crate::pokemon::*;
lazy_static! {
static ref DATA_DIR: PathBuf = "./data".into();
pub static ref TYPE_MAP: HashMap<u8, Type> = {
let types = read_pokedex();
let mut map = HashMap::new();
for type_data in types.members() {
if let Some(type_data) = Type::from_json(type_data) {
map.insert(type_data.id, type_data);
}
}
map
};
}
pub fn parse_json_file<P: AsRef<Path>>(path: P) -> JsonValue {
json::parse(fs::read_to_string(path).unwrap().as_ref()).unwrap()
}
pub fn read_natures() -> JsonValue {
parse_json_file(DATA_DIR.join("natures.json"))
}
pub fn read_pokedex() -> JsonValue {
parse_json_file(DATA_DIR.join("pokedex.json"))
}
pub fn read_types() -> JsonValue {
parse_json_file(DATA_DIR.join("types.json"))
}
pub fn read_characteristics() -> JsonValue {
parse_json_file(DATA_DIR.join("characteristics.json"))
}

5
src/lib.rs Normal file
View File

@ -0,0 +1,5 @@
pub mod database;
pub mod ivcalc;
pub mod ivpeek;
pub mod json;
pub mod pokemon;

View File

@ -2,7 +2,7 @@ use bevy::reflect::{FromReflect, Reflect};
use lazy_static::lazy_static;
use std::{collections::HashMap, fmt};
use crate::database::TYPE_MAP;
use crate::json::TYPE_MAP;
pub type BaseValue = u8;
pub type StatValue = i32;
@ -103,7 +103,7 @@ impl Pokemon {
}
}
pub fn get_url(&self) -> String {
pub fn sprite_url(&self) -> String {
format!(
"https://img.pokemondb.net/sprites/{gen}/normal/{name}.png",
gen = self.get_sprite_gen(),
@ -183,6 +183,18 @@ impl BaseStat {
BaseStat::Speed,
]
}
pub fn from_str(str: &str) -> Option<Self> {
match str {
"hp" => Some(BaseStat::Hp),
"attack" => Some(BaseStat::Attack),
"defense" => Some(BaseStat::Defense),
"special_attack" => Some(BaseStat::SpecialAttack),
"special_defense" => Some(BaseStat::SpecialDefense),
"speed" => Some(BaseStat::Speed),
_ => None,
}
}
}
impl GetKey for BaseStat {
@ -238,6 +250,7 @@ impl GetKey for Type {
#[derive(Default, Debug, Clone, Reflect, FromReflect, PartialEq)]
pub struct Nature {
pub id: u8,
pub pid: u8,
pub name: String,
pub alias: String,
pub increased: Option<BaseStat>,
@ -260,6 +273,7 @@ impl FromJson for Nature {
fn from_json(json: &json::JsonValue) -> Option<Self> {
Some(Self {
id: json["id"].as_u8()?,
pid: json["pid"].as_u8()?,
name: json["name"].as_str()?.to_string(),
alias: json["alias"].as_str()?.to_string(),
increased: json["increased"]