From e2743b9dc656a9778a9e2efc0deeba9bcd90c071 Mon Sep 17 00:00:00 2001 From: sergiotarxz Date: Mon, 11 Mar 2024 02:50:43 +0100 Subject: [PATCH] Adding ev and iv support. --- lib/GEmeTool/Save/Pokemon.pm | 193 ++++++++++++----- lib/GEmeTool/View/PokemonEditorWindow.pm | 259 +++++++++++++++++------ 2 files changed, 335 insertions(+), 117 deletions(-) diff --git a/lib/GEmeTool/Save/Pokemon.pm b/lib/GEmeTool/Save/Pokemon.pm index 8549a19..aa25c8b 100644 --- a/lib/GEmeTool/Save/Pokemon.pm +++ b/lib/GEmeTool/Save/Pokemon.pm @@ -13,33 +13,117 @@ use Rsaves::Constants::Emerald::SpeciesData; has _pokemon => ( is => 'rw', ); sub species { - my $self = shift; - my $arg = shift; - my $pokemon = $self->_pokemon; + my $self = shift; + my $arg = shift; + my $pokemon = $self->_pokemon; my $substruct_0 = $pokemon->{substructures}[0]; - if (defined $arg) { + if ( defined $arg ) { $substruct_0->{species} = $arg; } return $substruct_0->{species}; } +sub ivs { + my $self = shift; + my $arg = shift; + my $current_value = + $self->_pokemon->{substructures}[3]{ivs_egg_status_and_ability}; + my @stats = ( + 'HP', 'Attack', + 'Defense', 'Speed', + 'Special Attack', 'Special Defense' + ); + if ( defined $arg ) { + if ( defined $arg->{HP} ) { + $current_value &= ~0x1f; + $current_value |= ( $arg->{HP} & 0x1f ); + } + if ( defined $arg->{Attack} ) { + $current_value &= ~( 0x1f << 5 ); + $current_value |= ( ( $arg->{Attack} & 0x1f ) << 5 ); + } + if ( defined $arg->{Defense} ) { + $current_value &= ~( 0x1f << 10 ); + $current_value |= ( ( $arg->{Defense} & 0x1f ) << 10 ); + } + if ( defined $arg->{Speed} ) { + $current_value &= ~( 0x1f << 15 ); + $current_value |= ( ( $arg->{Speed} & 0x1f ) << 15 ); + } + if ( defined $arg->{SpecialAttack} ) { + $current_value &= ~( 0x1f << 20 ); + $current_value |= ( ( $arg->{SpecialAttack} & 0x1f ) << 20 ); + } + if ( defined $arg->{SpecialDefense} ) { + $current_value &= ~( 0x1f << 25 ); + $current_value |= ( ( $arg->{SpecialDefense} & 0x1f ) << 25 ); + } + $self->_pokemon->{substructures}[3]{ivs_egg_status_and_ability} = + $current_value; + } + my $ivs_egg_status_and_ability = $current_value; + return { + HP => $ivs_egg_status_and_ability & 0x1F, + Attack => ( $ivs_egg_status_and_ability >> 5 ) & 0x1F, + Defense => ( $ivs_egg_status_and_ability >> 10 ) & 0x1F, + Speed => ( $ivs_egg_status_and_ability >> 15 ) & 0x1F, + SpecialDefense => ( $ivs_egg_status_and_ability >> 25 ) & 0x1F, + SpecialAttack => ( $ivs_egg_status_and_ability >> 20 ) & 0x1F, + }; +} + +sub evs { + my $self = shift; + my $arg = shift; + my $pokemon = $self->_pokemon; + my $substructure = $pokemon->{substructures}[2]; + if (defined $arg) { + if (defined $arg->{HP}) { + $substructure->{hp_ev} = $arg->{HP}; + } + if (defined $arg->{Attack}) { + $substructure->{attack_ev} = $arg->{Attack}; + } + if (defined $arg->{Defense}) { + $substructure->{defense_ev} = $arg->{Defense}; + } + if (defined $arg->{Speed}) { + $substructure->{speed_ev} = $arg->{Speed}; + } + if (defined $arg->{SpecialAttack}) { + $substructure->{special_attack_ev} = $arg->{SpecialAttack}; + } + if (defined $arg->{SpecialDefense}) { + $substructure->{special_defense_ev} = $arg->{SpecialDefense}; + } + } + return { + HP => $pokemon->{substructures}[2]{hp_ev}, + Attack => $pokemon->{substructures}[2]{attack_ev}, + Defense => $pokemon->{substructures}[2]{defense_ev}, + Speed => $pokemon->{substructures}[2]{speed_ev}, + SpecialAttack => $pokemon->{substructures}[2]{special_attack_ev}, + SpecialDefense => $pokemon->{substructures}[2]{special_defense_ev}, + }; +} + sub level { my $self = shift; my $arg = shift; - if ( defined $arg ) { - die 'Not implemented'; - } - my $pokemon = $self->_pokemon; - my $experience = $pokemon->{substructures}[0]{experience}; + my $pokemon = $self->_pokemon; my $growth_func = sub { my $n = shift; - if ($n == 1) { + if ( $n == 1 ) { return 1; } return $self->growth_function->($n); }; + if ( defined $arg ) { + $pokemon->{substructures}[0]{experience} = $growth_func->($arg); + } + my $experience = $pokemon->{substructures}[0]{experience}; my $level = 1; - while ($level <= 100 && int($growth_func->($level)) <= $experience) { + while ( $level <= 100 && int( $growth_func->($level) ) <= $experience ) { $level++; } $level -= 1; @@ -48,75 +132,76 @@ sub level { } sub growth_function { - my $self = shift; + my $self = shift; my $growth = $self->growth; - if ($growth eq 'GROWTH_FAST') { + if ( $growth eq 'GROWTH_FAST' ) { return \&_exp_fast; } - if ($growth eq 'GROWTH_MEDIUM_FAST') { + if ( $growth eq 'GROWTH_MEDIUM_FAST' ) { return \&_exp_medium_fast; } - if ($growth eq 'GROWTH_MEDIUM_SLOW') { + if ( $growth eq 'GROWTH_MEDIUM_SLOW' ) { return \&_exp_medium_slow; } - if ($growth eq 'GROWTH_SLOW') { + if ( $growth eq 'GROWTH_SLOW' ) { return \&_exp_slow; } - if ($growth eq 'GROWTH_ERRATIC') { + if ( $growth eq 'GROWTH_ERRATIC' ) { return \&_exp_erratic; } - if ($growth eq 'GROWTH_FLUCTUATING') { + if ( $growth eq 'GROWTH_FLUCTUATING' ) { return \&_exp_fluctuating; } } sub gender_ratio { - my $self = shift; + my $self = shift; my $pokemon_name = $self->pokemon_name; my %pokemon_data = %Rsaves::Constants::Emerald::SpeciesData::SPECIES_DATA; - my $data = $pokemon_data{$pokemon_name}; + my $data = $pokemon_data{$pokemon_name}; my $gender_ratio = $data->{gender_ratio}; return $gender_ratio; } sub gender { + # 0 male # 1 female # 2 genderless - my $self = shift; - my $pokemon = $self->_pokemon; - my $personality = shift // $pokemon->{personality}; + my $self = shift; + my $pokemon = $self->_pokemon; + my $personality = shift // $pokemon->{personality}; my $gender_ratio = $self->gender_ratio; - if ($gender_ratio == 0) { + if ( $gender_ratio == 0 ) { return 0; } - if ($gender_ratio == 254) { + if ( $gender_ratio == 254 ) { return 1; } - if ($gender_ratio == 255) { + if ( $gender_ratio == 255 ) { return 2; } - if ($gender_ratio <= ($personality & 0xff)) { + if ( $gender_ratio <= ( $personality & 0xff ) ) { return 0; } return 1; } sub personality { - my $self = shift; - my $arg = shift; + my $self = shift; + my $arg = shift; my $pokemon = $self->_pokemon; - if (defined $arg) { + if ( defined $arg ) { $self->_pokemon->{personality} = $arg; } return $pokemon->{personality}; } sub growth { - my $self = shift; + my $self = shift; my $pokemon_name = $self->pokemon_name; my %pokemon_data = %Rsaves::Constants::Emerald::SpeciesData::SPECIES_DATA; - my $data = $pokemon_data{$pokemon_name}; + my $data = $pokemon_data{$pokemon_name}; return $data->{growth_rate}; } @@ -144,9 +229,10 @@ sub _exp_medium_fast { sub _exp_medium_slow { my $n = shift; + #define EXP_MEDIUM_SLOW(n)((6 * CUBE(n)) / 5 - (15 * SQUARE(n)) + (100 * n) - 140) // (6 * (n)^3) / 5 - (15 * (n)^2) + (100 * n) - 140 - my $return = ( - ( 6 * _cube($n) ) / 5 - ( 15 * _square($n) ) + ( 100 * $n ) - 140 ); + my $return = + ( ( 6 * _cube($n) ) / 5 - ( 15 * _square($n) ) + ( 100 * $n ) - 140 ); return $return; } @@ -201,46 +287,53 @@ sub get_icon { } sub generate_personality { - my $self = shift; - my $target_shiny = shift; - my $target_gender = shift; - my $target_nature = shift; - my $otid = $self->otid; + my $self = shift; + my $target_shiny = shift; + my $target_gender = shift; + my $target_nature = shift; + my $otid = $self->otid; my $should_search_gender = 0; my $personality; - if (defined $target_gender && !(grep {$self->gender_ratio eq $_} (0, 254, 255))) { + if ( defined $target_gender + && !( grep { $self->gender_ratio eq $_ } ( 0, 254, 255 ) ) ) + { $should_search_gender = 1; } - if (defined $target_gender && $target_gender != 0 && $target_gender != 1) { + if ( defined $target_gender && $target_gender != 0 && $target_gender != 1 ) + { die "Incorrect gender $target_gender."; } - if (defined $target_nature && ($target_nature < 0 || $target_nature > 24)) { + if ( defined $target_nature + && ( $target_nature < 0 || $target_nature > 24 ) ) + { die "Incorrect nature $target_nature."; } - for (my $i = 0; $i < 0xffffffff; $i++) { - if (defined $target_nature && $i % 25 != $target_nature) { + for ( my $i = 0 ; $i < 0xffffffff ; $i++ ) { + if ( defined $target_nature && $i % 25 != $target_nature ) { next; } - if (defined $target_shiny && !(!!$target_shiny == !!Rsaves::is_shiny($otid, $i))) { + if ( defined $target_shiny + && !( !!$target_shiny == !!Rsaves::is_shiny( $otid, $i ) ) ) + { next; } - if ($should_search_gender && $self->gender($i) != $target_gender) { + if ( $should_search_gender && $self->gender($i) != $target_gender ) { next; } $personality = $i; last; } - if (!defined $personality) { + if ( !defined $personality ) { warn "Could not find personality combination, this is a bug."; } return $personality; } sub otid { - my $self = shift; - my $arg = shift; + my $self = shift; + my $arg = shift; my $pokemon = $self->_pokemon; - if (defined $arg) { + if ( defined $arg ) { $pokemon->{otid} = $arg; } return $pokemon->{otid}; diff --git a/lib/GEmeTool/View/PokemonEditorWindow.pm b/lib/GEmeTool/View/PokemonEditorWindow.pm index 7c9390f..e4f23ef 100644 --- a/lib/GEmeTool/View/PokemonEditorWindow.pm +++ b/lib/GEmeTool/View/PokemonEditorWindow.pm @@ -41,10 +41,16 @@ has _save_callbacks => ( is => 'rw', default => sub { [] } ); has _current_personality => ( is => 'rw' ); +has _iv_buffers => ( is => 'rw', default => sub { {} } ); +has _ev_buffers => ( is => 'rw', default => sub { {} } ); + my $root = path(__FILE__)->parent->parent->parent->parent; my $width = 600; my $height = 400; +my @stats = + ( 'HP', 'Attack', 'Defense', 'Speed', 'SpecialAttack', 'SpecialDefense' ); + sub start { my $self = shift; my $window = Gtk4::Window->new; @@ -67,7 +73,7 @@ sub draw { my $pokemon = $self->pokemon; $window->set_title( 'Editing Pokémon ' . Rsaves::translate_3rd_encoding( $pokemon->nickname ) ); - $self->_current_personality($pokemon->personality); + $self->_current_personality( $pokemon->personality ); my $grid = Gtk4::Grid->new; $grid->set_column_homogeneous(1); my $canvas = Gtk4::DrawingArea->new; @@ -94,52 +100,165 @@ sub draw { my $string_list = Gtk4::StringList->new( [@species] ); my $save_button = Gtk4::Button->new_with_label('Save changes'); $self->_selected_species( $pokemon->species ); - my $box_right_image = Gtk4::Box->new('vertical', 1); + my $box_right_image = Gtk4::Box->new( 'vertical', 1 ); $box_right_image->set_margin_top(30); $box_right_image->set_valign('start'); $box_right_image->set_halign('start'); - $grid->attach($box_right_image, 2, 0, 4, 2); + my $box_ev = Gtk4::Box->new('vertical', 1); + $grid->attach( $box_right_image, 2, 0, 2, 2 ); + $grid->attach( $box_ev, 4, 0, 2, 2 ); + $box_ev->append(Gtk4::Label->new('Select EV')); + $self->create_select_level($box_ev); + $self->create_select_evs($box_ev); $self->create_change_nickname_entry($box_right_image); $save_button->signal_connect( clicked => sub { $pokemon->species( $self->_selected_species ); - $pokemon->personality($self->_current_personality); - for my $func (@{$self->_save_callbacks}) { + $pokemon->personality( $self->_current_personality ); + $self->recalculate_ivs; + $self->recalculate_evs; + for my $func ( @{ $self->_save_callbacks } ) { $func->(); } $self->draw; } ); $self->draw_dropdown_pokemon_list($box_right_image); + $box_right_image->append(Gtk4::Label->new('Select IV')); + $self->create_select_ivs($box_right_image); $self->create_modify_personality($grid); $grid->attach( $save_button, 4, 7, 1, 1 ); $window->set_child($grid); } +sub create_select_level { + my $self = shift; + my $box = shift; + my $pokemon = $self->pokemon; + my $box_level = Gtk4::Box->new('horizontal', 10); + $box_level->append(Gtk4::Label->new('Lvl:')); + my $entry = Gtk4::Entry->new; + $entry->set_input_purpose('digits'); + $entry->get_buffer->set_text($pokemon->level, length $pokemon->level); + $box_level->append($entry); + $self->onSave( sub { + my $text = $entry->get_buffer->get_text; + if ($text !~ /^[0-9]+$/) { + return; + } + if ($text > 100) { + $text = 100; + } + if ($text < 2) { + $text = 2; + } + $pokemon->level($text); + }); + $box->append($box_level); +} + +sub recalculate_evs { + my $self = shift; + my $pokemon = $self->pokemon; + for my $key ( @stats ) { + my $text = $self->_ev_buffers->{$key}->get_text; + next if $text !~ /^[0-9]+$/; + if ($text > 255) { + $text = 255; + } + if ($text < 0) { + $text = 0; + } + $pokemon->evs( + { + $key => $text, + } + ); + } +} + +sub recalculate_ivs { + my $self = shift; + my $pokemon = $self->pokemon; + for my $key ( @stats ) { + my $text = $self->_iv_buffers->{$key}->get_text; + next if $text !~ /^[0-9]+$/; + if ($text > 31) { + $text = 31; + } + if ($text < 0) { + $text = 0; + } + $pokemon->ivs( + { + $key => $text, + } + ); + } +} + +sub create_select_evs { + my $self = shift; + my $box = shift; + my $ev_buffers = $self->_ev_buffers; + my $evs = $self->pokemon->evs; + + for my $stat (@stats) { + my $box_stat = Gtk4::Box->new( 'horizontal', 10 ); + my $entry = Gtk4::Entry->new; + $entry->set_input_purpose('digits'); + $entry->get_buffer->set_text( $evs->{$stat}, length $evs->{$stat} ); + $box_stat->append( Gtk4::Label->new($stat) ); + $box_stat->append($entry); + $ev_buffers->{$stat} = $entry->get_buffer; + $box->append($box_stat); + } +} + +sub create_select_ivs { + my $self = shift; + my $box = shift; + my $iv_buffers = $self->_iv_buffers; + my $ivs = $self->pokemon->ivs; + + for my $stat (@stats) { + my $box_stat = Gtk4::Box->new( 'horizontal', 10 ); + my $entry = Gtk4::Entry->new; + $entry->set_input_purpose('digits'); + $entry->get_buffer->set_text( $ivs->{$stat}, length $ivs->{$stat} ); + $box_stat->append( Gtk4::Label->new($stat) ); + $box_stat->append($entry); + $iv_buffers->{$stat} = $entry->get_buffer; + $box->append($box_stat); + } +} + sub create_modify_personality { my $self = shift; - my $grid = shift; + my $grid = shift; my $button = Gtk4::Button->new_with_label('Edit personality'); - $button->signal_connect(clicked => sub { - $self->open_window_personality; - }); + $button->signal_connect( + clicked => sub { + $self->open_window_personality; + } + ); $button->set_valign('start'); $button->set_halign('start'); - $grid->attach($button, 2, 2, 1, 1); + $grid->attach( $button, 2, 2, 1, 1 ); } sub open_window_personality { - my $self = shift; + my $self = shift; my $window = Gtk4::Window->new; - $window->set_default_size(600, 600); - my $scroll = Gtk4::ScrolledWindow->new; - my $box_window = Gtk4::Box->new('vertical', 1); - $box_window->append(Gtk4::Label->new('Select shiny')); + $window->set_default_size( 600, 600 ); + my $scroll = Gtk4::ScrolledWindow->new; + my $box_window = Gtk4::Box->new( 'vertical', 1 ); + $box_window->append( Gtk4::Label->new('Select shiny') ); - my $box_shiny = Gtk4::Box->new('horizontal', 1); + my $box_shiny = Gtk4::Box->new( 'horizontal', 1 ); my $shiny_dont_mind = Gtk4::ToggleButton->new_with_label('Do not matter'); - my $shiny_yes = Gtk4::ToggleButton->new_with_label('Yes'); - my $shiny_no = Gtk4::ToggleButton->new_with_label('No'); + my $shiny_yes = Gtk4::ToggleButton->new_with_label('Yes'); + my $shiny_no = Gtk4::ToggleButton->new_with_label('No'); $shiny_dont_mind->set_active(1); @@ -151,12 +270,12 @@ sub open_window_personality { $box_shiny->append($shiny_no); $box_window->append($box_shiny); - $box_window->append(Gtk4::Label->new('Select gender')); + $box_window->append( Gtk4::Label->new('Select gender') ); - my $box_gender = Gtk4::Box->new('horizontal', 1); + my $box_gender = Gtk4::Box->new( 'horizontal', 1 ); my $gender_dont_mind = Gtk4::ToggleButton->new_with_label('Do not matter'); - my $gender_male = Gtk4::ToggleButton->new_with_label('Male'); - my $gender_female = Gtk4::ToggleButton->new_with_label('Female'); + my $gender_male = Gtk4::ToggleButton->new_with_label('Male'); + my $gender_female = Gtk4::ToggleButton->new_with_label('Female'); $gender_female->set_group($gender_male); $gender_dont_mind->set_group($gender_male); @@ -169,11 +288,12 @@ sub open_window_personality { $box_window->append($box_gender); - $box_window->append(Gtk4::Label->new('Select nature')); + $box_window->append( Gtk4::Label->new('Select nature') ); - my $box_natures = Gtk4::Box->new('vertical', 1); - my @natures = @Rsaves::Constants::Emerald::Natures::NATURES; - my %toggle_nature = map { $_ => Gtk4::ToggleButton->new_with_label($_) } @natures; + my $box_natures = Gtk4::Box->new( 'vertical', 1 ); + my @natures = @Rsaves::Constants::Emerald::Natures::NATURES; + my %toggle_nature = + map { $_ => Gtk4::ToggleButton->new_with_label($_) } @natures; my $nature_dont_mind = Gtk4::ToggleButton->new_with_label('Do not matter'); $nature_dont_mind->set_active(1); @@ -189,35 +309,39 @@ sub open_window_personality { $box_window->append($box_natures); my $button_generate = Gtk4::Button->new_with_label('Generate personality'); - $button_generate->signal_connect(clicked => sub { - my @gender_buttons = ($gender_male, $gender_female); - my $target_shiny = undef; - my $target_gender = undef; - my $target_nature = undef; - if ($shiny_yes->get_active) { - $target_shiny = 1; - } - if ($shiny_no->get_active) { - $target_shiny = 0; - } - for my $i (0..1) { - if ($gender_buttons[$i]->get_active) { - $target_gender = $i; - last; + $button_generate->signal_connect( + clicked => sub { + my @gender_buttons = ( $gender_male, $gender_female ); + my $target_shiny = undef; + my $target_gender = undef; + my $target_nature = undef; + if ( $shiny_yes->get_active ) { + $target_shiny = 1; } - } - for my $i (0..24) { - if ($toggle_nature{$natures[$i]}->get_active) { - $target_nature = $i; - last; + if ( $shiny_no->get_active ) { + $target_shiny = 0; } + for my $i ( 0 .. 1 ) { + if ( $gender_buttons[$i]->get_active ) { + $target_gender = $i; + last; + } + } + for my $i ( 0 .. 24 ) { + if ( $toggle_nature{ $natures[$i] }->get_active ) { + $target_nature = $i; + last; + } + } + my $personality = + $self->pokemon->generate_personality( $target_shiny, + $target_gender, $target_nature ); + if ( defined $personality ) { + $self->_current_personality($personality); + } + $window->close; } - my $personality = $self->pokemon->generate_personality($target_shiny, $target_gender, $target_nature); - if (defined $personality) { - $self->_current_personality($personality); - } - $window->close; - }); + ); $box_window->append($button_generate); $scroll->set_child($box_window); $window->set_child($scroll); @@ -225,28 +349,29 @@ sub open_window_personality { } sub create_change_nickname_entry { - my $self = shift; - my $box = shift; - my $pokemon = $self->pokemon; - my $label = Gtk4::Label->new('Change the nickname:'); - my $entry = Gtk4::Entry->new; - my $nickname = Rsaves::translate_3rd_encoding( $pokemon->nickname ); - my $box_nickname = Gtk4::Box->new('horizontal', 10); + my $self = shift; + my $box = shift; + my $pokemon = $self->pokemon; + my $label = Gtk4::Label->new('Change the nickname:'); + my $entry = Gtk4::Entry->new; + my $nickname = Rsaves::translate_3rd_encoding( $pokemon->nickname ); + my $box_nickname = Gtk4::Box->new( 'horizontal', 10 ); $entry->get_buffer->set_text( $nickname, length $nickname ); $entry->set_halign('start'); $entry->set_valign('start'); $label->set_halign('start'); $label->set_valign('start'); - $box_nickname->append( $label); - $box_nickname->append( $entry); + $box_nickname->append($label); + $box_nickname->append($entry); $box->append($box_nickname); $self->onSave( sub { - my $translated_nickname = Rsaves::to_3rd_encoding($entry->get_buffer->get_text); + my $translated_nickname = + Rsaves::to_3rd_encoding( $entry->get_buffer->get_text ); if ( length $translated_nickname < 10 ) { $translated_nickname .= chr(0xff); my $length = length $translated_nickname; - $translated_nickname .= (chr(0x00)) x (10 - $length); + $translated_nickname .= ( chr(0x00) ) x ( 10 - $length ); } if ( length $translated_nickname > 10 ) { $translated_nickname = substr $translated_nickname, 0, 10; @@ -259,7 +384,7 @@ sub create_change_nickname_entry { sub draw_dropdown_pokemon_list { my $self = shift; - my $box = shift; + my $box = shift; my $pokemon = $self->pokemon; my $button_box = Gtk4::Box->new( 'horizontal', 1 ); my $box_popover = Gtk4::Box->new( 'vertical', 1 ); @@ -309,9 +434,9 @@ sub draw_dropdown_pokemon_list { $popover_dropdown->set_size_request( 100, 400 ); $button_box->append($popover_dropdown); my $label_button = Gtk4::Label->new('Change species:'); - my $box_dropdown = Gtk4::Box->new('horizontal', 10); - $box_dropdown->append( $label_button); - $box_dropdown->append( $button); + my $box_dropdown = Gtk4::Box->new( 'horizontal', 10 ); + $box_dropdown->append($label_button); + $box_dropdown->append($button); $box->append($box_dropdown); $label_button->set_valign('start'); $label_button->set_halign('start');