added iv calculation

master
hheik 2023-03-21 04:01:56 +02:00
parent 089eb8917a
commit 75286df87c
3 changed files with 390 additions and 83 deletions

View File

@ -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(

View File

@ -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
View File

@ -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()
}