added iv calculation
parent
089eb8917a
commit
75286df87c
|
|
@ -1,11 +1,7 @@
|
|||
use bevy::prelude::*;
|
||||
use bevy_egui::EguiContexts;
|
||||
|
||||
use crate::{
|
||||
database::{load_url_asset, Database},
|
||||
pokemon::*,
|
||||
ui::UiAssets,
|
||||
};
|
||||
use crate::{database::load_url_asset, pokemon::*, ui::UiAssets};
|
||||
|
||||
pub struct InspectorPlugin;
|
||||
|
||||
|
|
@ -13,7 +9,6 @@ impl Plugin for InspectorPlugin {
|
|||
fn build(&self, app: &mut App) {
|
||||
app.register_type::<InspectorPokemon>()
|
||||
.insert_resource(InspectorPokemon::default())
|
||||
.add_startup_system(resource_setup)
|
||||
.add_system(state_changed);
|
||||
}
|
||||
}
|
||||
|
|
@ -24,10 +19,7 @@ pub struct InspectorPokemon {
|
|||
pub pokemon: Option<Pokemon>,
|
||||
pub nature: Option<Nature>,
|
||||
pub characteristic: Option<Characteristic>,
|
||||
}
|
||||
|
||||
fn resource_setup(mut inspector: ResMut<InspectorPokemon>, pokemon: Res<Database<Pokemon>>) {
|
||||
inspector.pokemon = pokemon.map.get("sceptile").cloned();
|
||||
pub derived_stats: DerivedStats,
|
||||
}
|
||||
|
||||
fn state_changed(
|
||||
|
|
|
|||
117
src/pokemon.rs
117
src/pokemon.rs
|
|
@ -5,6 +5,7 @@ use std::{collections::HashMap, fmt};
|
|||
use crate::database::TYPE_MAP;
|
||||
|
||||
pub type BaseValue = u8;
|
||||
pub type StatValue = i32;
|
||||
|
||||
lazy_static! {
|
||||
pub static ref BASESTAT_MAP: HashMap<String, BaseStat> = {
|
||||
|
|
@ -29,10 +30,39 @@ pub trait GetKey {
|
|||
fn key(&self) -> String;
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone)]
|
||||
#[derive(Default, Debug, Clone, Reflect, FromReflect)]
|
||||
pub struct DerivedStats {
|
||||
pub level: Option<i32>,
|
||||
pub stats: HashMap<BaseStat, i32>,
|
||||
pub level: Option<StatValue>,
|
||||
pub hp: Option<StatValue>,
|
||||
pub attack: Option<StatValue>,
|
||||
pub defense: Option<StatValue>,
|
||||
pub special_attack: Option<StatValue>,
|
||||
pub special_defense: Option<StatValue>,
|
||||
pub speed: Option<StatValue>,
|
||||
}
|
||||
|
||||
impl DerivedStats {
|
||||
pub fn value(&self, stat: BaseStat) -> Option<StatValue> {
|
||||
match stat {
|
||||
BaseStat::Hp => self.hp,
|
||||
BaseStat::Attack => self.attack,
|
||||
BaseStat::Defense => self.defense,
|
||||
BaseStat::SpecialAttack => self.special_attack,
|
||||
BaseStat::SpecialDefense => self.special_defense,
|
||||
BaseStat::Speed => self.speed,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn value_mut(&mut self, stat: BaseStat) -> &mut Option<StatValue> {
|
||||
match stat {
|
||||
BaseStat::Hp => &mut self.hp,
|
||||
BaseStat::Attack => &mut self.attack,
|
||||
BaseStat::Defense => &mut self.defense,
|
||||
BaseStat::SpecialAttack => &mut self.special_attack,
|
||||
BaseStat::SpecialDefense => &mut self.special_defense,
|
||||
BaseStat::Speed => &mut self.speed,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default, Debug, Clone, Reflect, FromReflect, PartialEq)]
|
||||
|
|
@ -126,7 +156,7 @@ impl GetKey for Pokemon {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Reflect, FromReflect, Default, PartialEq)]
|
||||
#[derive(Debug, Clone, Copy, Reflect, FromReflect, Default, Eq, PartialEq, Hash)]
|
||||
pub enum BaseStat {
|
||||
#[default]
|
||||
Hp,
|
||||
|
|
@ -183,6 +213,18 @@ pub struct Nature {
|
|||
pub decreased: Option<BaseStat>,
|
||||
}
|
||||
|
||||
impl Nature {
|
||||
pub fn stat_mult(&self, stat: BaseStat) -> f32 {
|
||||
if self.increased == Some(stat) {
|
||||
1.1
|
||||
} else if self.decreased == Some(stat) {
|
||||
0.9
|
||||
} else {
|
||||
1.0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FromJson for Nature {
|
||||
fn from_json(json: &json::JsonValue) -> Option<Self> {
|
||||
Some(Self {
|
||||
|
|
@ -232,3 +274,70 @@ impl GetKey for Characteristic {
|
|||
self.name.to_lowercase()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn calculate_possible_ivs(
|
||||
base_stat: BaseStat,
|
||||
base_value: BaseValue,
|
||||
derived_value: StatValue,
|
||||
ev: u8,
|
||||
level: StatValue,
|
||||
nature: Option<&Nature>,
|
||||
characteristic: Option<&Characteristic>,
|
||||
) -> Result<Vec<u8>, String> {
|
||||
let mut possible_values = vec![];
|
||||
|
||||
let mut iv_range: Vec<_> = (0..32).collect();
|
||||
if let Some(characteristic) = characteristic {
|
||||
if characteristic.stat == base_stat {
|
||||
iv_range = characteristic.possible_values.clone();
|
||||
}
|
||||
};
|
||||
|
||||
for iv in iv_range {
|
||||
if calculate_derived_stat(
|
||||
base_stat,
|
||||
base_value,
|
||||
iv,
|
||||
ev,
|
||||
level,
|
||||
&nature.cloned().unwrap_or_default(),
|
||||
) == derived_value
|
||||
{
|
||||
possible_values.push(iv);
|
||||
}
|
||||
}
|
||||
Ok(possible_values)
|
||||
}
|
||||
|
||||
fn calculate_derived_stat(
|
||||
base_stat: BaseStat,
|
||||
base_value: BaseValue,
|
||||
iv: u8,
|
||||
ev: u8,
|
||||
level: StatValue,
|
||||
nature: &Nature,
|
||||
) -> StatValue {
|
||||
match base_stat {
|
||||
BaseStat::Hp => {
|
||||
if base_value == 1 {
|
||||
return 1; // Shedinja
|
||||
}
|
||||
(((2 * base_value as i32 + iv as i32) as f32 + (ev as f32 / 4.0).floor())
|
||||
* level as f32
|
||||
/ 100.0)
|
||||
.floor() as StatValue
|
||||
+ level
|
||||
+ 10
|
||||
}
|
||||
_ => {
|
||||
let nature_mult = nature.stat_mult(base_stat);
|
||||
(((((2 * base_value as i32 + iv as i32) as f32 + (ev as f32 / 4.0).floor())
|
||||
* level as f32
|
||||
/ 100.0)
|
||||
.floor() as StatValue
|
||||
+ 5) as f32
|
||||
* nature_mult)
|
||||
.floor() as StatValue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
276
src/ui.rs
276
src/ui.rs
|
|
@ -1,11 +1,14 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
use bevy::prelude::*;
|
||||
use bevy_egui::{egui, EguiContexts, EguiPlugin};
|
||||
use lazy_static::lazy_static;
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::{database::Database, inspector::InspectorPokemon, pokemon::*};
|
||||
|
||||
const INPUT_LABEL_WIDTH: f32 = 100.0;
|
||||
const INPUT_TEXT_WIDTH: f32 = 150.0;
|
||||
const BAR_HEIGHT: f32 = 12.0;
|
||||
|
||||
lazy_static! {
|
||||
static ref BASE_STAT_COLOR_RANGES: Vec<(u8, egui::Color32)> = vec![
|
||||
(0, egui::Color32::from_rgb(255, 0, 0)),
|
||||
|
|
@ -16,6 +19,21 @@ lazy_static! {
|
|||
(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![
|
||||
8.0, // Nature modifier
|
||||
25.0, // Base stat
|
||||
255.0, // Base stat bar
|
||||
60.0, // Stat name
|
||||
40.0, // Derived stat input
|
||||
30.0, // possible IV range
|
||||
];
|
||||
}
|
||||
|
||||
pub struct UiPlugin;
|
||||
|
|
@ -37,10 +55,11 @@ pub struct UiAssets {
|
|||
pub sprite_map: HashMap<String, (Handle<Image>, egui::TextureId)>,
|
||||
}
|
||||
|
||||
#[derive(Resource, Default, Reflect)]
|
||||
#[reflect(Resource)]
|
||||
#[derive(Resource, Default)]
|
||||
pub struct UiState {
|
||||
pub name: String,
|
||||
pub level: String,
|
||||
pub derived_stats: HashMap<BaseStat, String>,
|
||||
}
|
||||
|
||||
fn load_assets(mut ui_assets: ResMut<UiAssets>, assets: Res<AssetServer>) {
|
||||
|
|
@ -75,8 +94,16 @@ fn ui_system(
|
|||
}
|
||||
|
||||
egui::CentralPanel::default().show(contexts.ctx_mut(), |ui| {
|
||||
const INPUT_LABEL_WIDTH: f32 = 100.0;
|
||||
const INPUT_TEXT_WIDTH: f32 = 150.0;
|
||||
let sprite_size = egui::Vec2::new(128., 128.);
|
||||
|
||||
let pokemon = inspector.pokemon.clone();
|
||||
let pokemon = pokemon.as_ref();
|
||||
let nature = inspector.nature.clone();
|
||||
let nature = nature.as_ref();
|
||||
let characteristic = inspector.characteristic.clone();
|
||||
let characteristic = characteristic.as_ref();
|
||||
|
||||
// Pokemon
|
||||
ui.horizontal(|ui| {
|
||||
ui.horizontal(|ui| {
|
||||
ui.set_width(INPUT_LABEL_WIDTH);
|
||||
|
|
@ -87,17 +114,16 @@ fn ui_system(
|
|||
ui.text_edit_singleline(&mut ui_state.name);
|
||||
});
|
||||
});
|
||||
ui.add_space(2.);
|
||||
|
||||
// Nature
|
||||
ui.horizontal(|ui| {
|
||||
ui.horizontal(|ui| {
|
||||
ui.set_width(INPUT_LABEL_WIDTH);
|
||||
ui.label("nature:");
|
||||
});
|
||||
egui::ComboBox::new("nature", "")
|
||||
.selected_text(format!(
|
||||
"{}",
|
||||
inspector.nature.as_ref().map_or("-", |c| &c.name)
|
||||
))
|
||||
.selected_text(format!("{}", nature.map_or("-", |c| &c.name)))
|
||||
.show_ui(ui, |ui| {
|
||||
ui.selectable_value(&mut inspector.nature, None, "-");
|
||||
let mut chars: Vec<_> = natures.map.values().collect();
|
||||
|
|
@ -107,17 +133,16 @@ fn ui_system(
|
|||
}
|
||||
});
|
||||
});
|
||||
ui.add_space(2.);
|
||||
|
||||
// Characteristics
|
||||
ui.horizontal(|ui| {
|
||||
ui.horizontal(|ui| {
|
||||
ui.set_width(INPUT_LABEL_WIDTH);
|
||||
ui.label("characteristic:");
|
||||
});
|
||||
egui::ComboBox::new("characteristic", "")
|
||||
.selected_text(format!(
|
||||
"{}",
|
||||
inspector.characteristic.as_ref().map_or("-", |c| &c.name)
|
||||
))
|
||||
.selected_text(format!("{}", characteristic.map_or("-", |c| &c.name)))
|
||||
.show_ui(ui, |ui| {
|
||||
ui.selectable_value(&mut inspector.characteristic, None, "-");
|
||||
let mut chars: Vec<_> = characteristics.map.values().collect();
|
||||
|
|
@ -132,19 +157,85 @@ fn ui_system(
|
|||
});
|
||||
});
|
||||
|
||||
if let Some(pokemon) = inspector.pokemon.as_ref() {
|
||||
ui.heading(egui::RichText::new(pokemon.full_name.clone()));
|
||||
ui.add_space(4.);
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
// Sprite
|
||||
ui.horizontal(|ui| {
|
||||
ui.set_min_size(sprite_size);
|
||||
if let Some((_, rendered_texture_id)) =
|
||||
pokemon.map_or(None, |pkmn| ui_assets.sprite_map.get(&pkmn.key()))
|
||||
{
|
||||
ui.add(egui::Image::new(*rendered_texture_id, sprite_size));
|
||||
}
|
||||
});
|
||||
|
||||
// if let Some(pokemon) = pokemon {
|
||||
ui.vertical(|ui| {
|
||||
ui.add_space(sprite_size.y / 2. - 24.);
|
||||
|
||||
// Name
|
||||
ui.heading(
|
||||
egui::RichText::new(pokemon.map_or("-", |pkmn| &pkmn.full_name))
|
||||
.color(egui::Color32::LIGHT_GRAY),
|
||||
);
|
||||
|
||||
// Types
|
||||
ui.label(format!(
|
||||
"{} / {}",
|
||||
pokemon.type1.as_ref().map_or("-", |t| t.name.as_str()),
|
||||
pokemon.type2.as_ref().map_or("-", |t| t.name.as_str())
|
||||
pokemon.map_or("-", |pkmn| pkmn
|
||||
.type1
|
||||
.as_ref()
|
||||
.map_or("-", |t| t.name.as_str())),
|
||||
pokemon.map_or("-", |pkmn| pkmn
|
||||
.type2
|
||||
.as_ref()
|
||||
.map_or("-", |t| t.name.as_str()))
|
||||
));
|
||||
});
|
||||
// };
|
||||
});
|
||||
|
||||
if let Some((_, rendered_texture_id)) = ui_assets.sprite_map.get(&pokemon.key()) {
|
||||
ui.add(egui::Image::new(*rendered_texture_id, [128., 128.]));
|
||||
ui.add_space(4.);
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
for (i, width) in COLUMNS.iter().enumerate() {
|
||||
ui.horizontal(|ui| {
|
||||
ui.set_width(*width);
|
||||
match i {
|
||||
4 => {
|
||||
let value = &mut ui_state.level;
|
||||
if ui.add(egui::TextEdit::singleline(value)).changed() {
|
||||
*value = digit_filter(value);
|
||||
if let Ok(value) = value.parse::<StatValue>() {
|
||||
inspector.derived_stats.level = Some(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
for (i, width) in COLUMNS.iter().enumerate() {
|
||||
ui.horizontal(|ui| {
|
||||
ui.set_width(*width);
|
||||
match i {
|
||||
4 => {
|
||||
ui.label(egui::RichText::new("Stat"));
|
||||
}
|
||||
5 => {
|
||||
ui.label(egui::RichText::new("IV range"));
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Stats
|
||||
for stat in vec![
|
||||
BaseStat::Hp,
|
||||
BaseStat::Attack,
|
||||
|
|
@ -153,15 +244,14 @@ fn ui_system(
|
|||
BaseStat::SpecialDefense,
|
||||
BaseStat::Speed,
|
||||
] {
|
||||
let mut column_size = COLUMNS.iter();
|
||||
ui.horizontal(|ui| {
|
||||
// Nature modifier
|
||||
ui.horizontal(|ui| {
|
||||
ui.set_width(60.0);
|
||||
ui.label(format!("{stat}"));
|
||||
});
|
||||
ui.horizontal(|ui| {
|
||||
ui.horizontal(|ui| {
|
||||
ui.set_width(8.0);
|
||||
let symbol = inspector.nature.as_ref().map_or("", |n| {
|
||||
if let Some(width) = column_size.next() {
|
||||
ui.set_width(*width);
|
||||
}
|
||||
let symbol = nature.map_or("", |n| {
|
||||
if n.increased == Some(stat) {
|
||||
"+"
|
||||
} else if n.decreased == Some(stat) {
|
||||
|
|
@ -172,13 +262,24 @@ fn ui_system(
|
|||
});
|
||||
ui.label(symbol);
|
||||
});
|
||||
});
|
||||
|
||||
// Base stat number
|
||||
ui.horizontal(|ui| {
|
||||
ui.set_width(25.0);
|
||||
ui.label(pokemon.base_value(stat).to_string());
|
||||
if let Some(width) = column_size.next() {
|
||||
ui.set_width(*width);
|
||||
}
|
||||
let base_stat = &pokemon
|
||||
.map_or(String::from("-"), |pkmn| pkmn.base_value(stat).to_string());
|
||||
ui.label(egui::RichText::new(base_stat));
|
||||
});
|
||||
|
||||
// Base stat bar
|
||||
ui.horizontal(|ui| {
|
||||
let base_value = pokemon.base_value(stat);
|
||||
if let Some(width) = column_size.next() {
|
||||
ui.set_width(*width);
|
||||
}
|
||||
let base_value = pokemon.map_or(0, |pkmn| pkmn.base_value(stat));
|
||||
let bar_length = base_value as f32;
|
||||
let mut color = BASE_STAT_COLOR_RANGES.first().unwrap().1;
|
||||
for (treshold, c) in BASE_STAT_COLOR_RANGES.iter() {
|
||||
if base_value >= *treshold {
|
||||
|
|
@ -188,14 +289,119 @@ fn ui_system(
|
|||
}
|
||||
}
|
||||
|
||||
let image =
|
||||
egui::Image::new(*rendered_texture_id, [base_value as f32, 12.])
|
||||
let image = egui::Image::new(*rendered_texture_id, [bar_length, BAR_HEIGHT])
|
||||
.tint(color);
|
||||
ui.set_width(260.0);
|
||||
ui.add(image);
|
||||
});
|
||||
});
|
||||
|
||||
// Stat name
|
||||
ui.horizontal(|ui| {
|
||||
if let Some(width) = column_size.next() {
|
||||
ui.set_width(*width);
|
||||
}
|
||||
ui.label(
|
||||
egui::RichText::new(format!("{stat}")).color(nature_color(stat, nature)),
|
||||
);
|
||||
});
|
||||
|
||||
// Derived stat input
|
||||
ui.horizontal(|ui| {
|
||||
if let Some(width) = column_size.next() {
|
||||
ui.set_width(*width);
|
||||
}
|
||||
if !ui_state.derived_stats.contains_key(&stat) {
|
||||
ui_state.derived_stats.insert(stat, "0".to_string());
|
||||
}
|
||||
let value = ui_state.derived_stats.get_mut(&stat).unwrap();
|
||||
if ui.add(egui::TextEdit::singleline(value)).changed() {
|
||||
*value = digit_filter(value);
|
||||
if let (Ok(value), derived) = (
|
||||
value.parse::<StatValue>(),
|
||||
inspector.derived_stats.value_mut(stat),
|
||||
) {
|
||||
*derived = Some(value);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let possible_ivs = if let (Some(pokemon), Some(level), Some(derived_stat)) = (
|
||||
pokemon,
|
||||
inspector.derived_stats.level,
|
||||
inspector.derived_stats.value(stat),
|
||||
) {
|
||||
calculate_possible_ivs(
|
||||
stat,
|
||||
pokemon.base_value(stat),
|
||||
derived_stat,
|
||||
0,
|
||||
level,
|
||||
nature,
|
||||
characteristic,
|
||||
)
|
||||
} else {
|
||||
Ok(vec![])
|
||||
};
|
||||
|
||||
// Possible IV range
|
||||
ui.horizontal(|ui| match possible_ivs {
|
||||
Ok(possible_ivs) => {
|
||||
if possible_ivs.len() == 0 {
|
||||
ui.label(egui::RichText::new("?"));
|
||||
} else if possible_ivs.len() <= 10 {
|
||||
for iv in possible_ivs.iter() {
|
||||
ui.label(
|
||||
egui::RichText::new(format!("{}", iv))
|
||||
.color(iv_color(*iv as u8)),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
let first = possible_ivs.first().unwrap();
|
||||
let last = possible_ivs.last().unwrap();
|
||||
ui.label(
|
||||
egui::RichText::new(format!("{}", first))
|
||||
.color(iv_color(*first as u8)),
|
||||
);
|
||||
ui.label(egui::RichText::new("-"));
|
||||
ui.label(
|
||||
egui::RichText::new(format!("{}", last))
|
||||
.color(iv_color(*last as u8)),
|
||||
);
|
||||
}
|
||||
}
|
||||
Err(reason) => {
|
||||
ui.label(egui::RichText::new(reason).color(egui::Color32::RED));
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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())
|
||||
}
|
||||
|
||||
fn digit_filter(string: &str) -> String {
|
||||
string.chars().filter(|v| v.is_digit(10)).collect()
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue