419 lines
15 KiB
Rust
419 lines
15 KiB
Rust
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)),
|
|
(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![
|
|
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;
|
|
|
|
impl Plugin for UiPlugin {
|
|
fn build(&self, app: &mut App) {
|
|
app.add_plugin(EguiPlugin)
|
|
.insert_resource(UiAssets::default())
|
|
.insert_resource(UiState::default())
|
|
.add_startup_system(load_assets)
|
|
.add_system(handle_state)
|
|
.add_system(ui_system);
|
|
}
|
|
}
|
|
|
|
#[derive(Resource, Default)]
|
|
pub struct UiAssets {
|
|
pub bar_handle: Handle<Image>,
|
|
pub sprite_map: HashMap<String, (Handle<Image>, egui::TextureId)>,
|
|
}
|
|
|
|
#[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>) {
|
|
ui_assets.bar_handle = assets.load("ui/bar.png");
|
|
}
|
|
|
|
fn handle_state(
|
|
ui_state: Res<UiState>,
|
|
pokemon: Res<Database<Pokemon>>,
|
|
mut inspector: ResMut<InspectorPokemon>,
|
|
) {
|
|
if ui_state.is_changed() {
|
|
if let Some(pokemon) = pokemon.map.get(ui_state.name.to_lowercase().as_str()) {
|
|
inspector.pokemon = Some(pokemon.clone());
|
|
}
|
|
}
|
|
}
|
|
|
|
fn ui_system(
|
|
mut contexts: EguiContexts,
|
|
mut inspector: ResMut<InspectorPokemon>,
|
|
ui_assets: Res<UiAssets>,
|
|
mut ui_state: ResMut<UiState>,
|
|
mut rendered_texture_id: Local<egui::TextureId>,
|
|
mut is_initialized: Local<bool>,
|
|
characteristics: Res<Database<Characteristic>>,
|
|
natures: Res<Database<Nature>>,
|
|
) {
|
|
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.);
|
|
|
|
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);
|
|
ui.label("pokemon:");
|
|
});
|
|
ui.horizontal(|ui| {
|
|
ui.set_width(INPUT_TEXT_WIDTH);
|
|
let input = ui.text_edit_singleline(&mut ui_state.name);
|
|
if input.gained_focus() {
|
|
ui_state.name = String::from("");
|
|
}
|
|
});
|
|
});
|
|
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!("{}", 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();
|
|
chars.sort_by_key(|c| c.name.clone());
|
|
for char in chars {
|
|
ui.selectable_value(&mut inspector.nature, Some(char.clone()), &char.name);
|
|
}
|
|
});
|
|
});
|
|
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!("{}", 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();
|
|
chars.sort_by_key(|c| c.name.clone());
|
|
for char in chars {
|
|
ui.selectable_value(
|
|
&mut inspector.characteristic,
|
|
Some(char.clone()),
|
|
&char.name,
|
|
);
|
|
}
|
|
});
|
|
});
|
|
|
|
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.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()))
|
|
));
|
|
});
|
|
// };
|
|
});
|
|
|
|
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;
|
|
let input = ui.add(egui::TextEdit::singleline(value));
|
|
if input.changed() {
|
|
*value = digit_filter(value);
|
|
if let Ok(value) = value.parse::<StatValue>() {
|
|
inspector.derived_stats.level = Some(value);
|
|
}
|
|
}
|
|
if input.gained_focus() {
|
|
*value = String::from("");
|
|
}
|
|
}
|
|
_ => (),
|
|
}
|
|
});
|
|
}
|
|
});
|
|
|
|
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,
|
|
BaseStat::Defense,
|
|
BaseStat::SpecialAttack,
|
|
BaseStat::SpecialDefense,
|
|
BaseStat::Speed,
|
|
] {
|
|
let mut column_size = COLUMNS.iter();
|
|
ui.horizontal(|ui| {
|
|
// Nature modifier
|
|
ui.horizontal(|ui| {
|
|
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) {
|
|
"-"
|
|
} else {
|
|
""
|
|
}
|
|
});
|
|
ui.label(symbol);
|
|
});
|
|
|
|
// Base stat number
|
|
ui.horizontal(|ui| {
|
|
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| {
|
|
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 {
|
|
color = c.clone();
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
let image = egui::Image::new(*rendered_texture_id, [bar_length, BAR_HEIGHT])
|
|
.tint(color);
|
|
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, "".to_string());
|
|
}
|
|
let value = ui_state.derived_stats.get_mut(&stat).unwrap();
|
|
let input = ui.add(egui::TextEdit::singleline(value));
|
|
if input.changed() {
|
|
*value = digit_filter(value);
|
|
if let (Ok(value), derived) = (
|
|
value.parse::<StatValue>(),
|
|
inspector.derived_stats.value_mut(stat),
|
|
) {
|
|
*derived = Some(value);
|
|
}
|
|
}
|
|
if input.gained_focus() {
|
|
*value = String::from("");
|
|
}
|
|
});
|
|
|
|
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()
|
|
}
|