Adding non destructive pokemon edition.

This commit is contained in:
sergiotarxz 2024-03-12 20:06:05 +01:00
parent 7ebbc18eaa
commit 65fde508f1
4 changed files with 212 additions and 85 deletions

View File

@ -23,6 +23,13 @@ sub MIGRATIONS {
backup_input_file TEXT,
backup_output_file TEXT
);',
'CREATE TABLE backups_pk3 (
uuid TEXT PRIMARY KEY,
date datetime NOT NULL,
nickname TEXT,
pokemon_name TEXT,
file TEXT
);',
);
}
1;

View File

@ -74,6 +74,8 @@ sub backup_files {
next;
}
my $basename = $file->basename;
$basename =~ s/\.\.//g;
$basename =~ s/\///g;
my $output_file = $backup_dir->child("$digest-$basename");
$output_file->spew_raw($contents);
push @result_files, $output_file;

View File

@ -10,8 +10,12 @@ use Rsaves;
use Rsaves::Constants::Emerald::Species;
use Rsaves::Constants::Emerald::SpeciesData;
use Path::Tiny;
use GEmeTool::DB;
use GEmeTool::Config;
use Digest::SHA qw/sha256_hex/;
use UUID::URandom qw/create_uuid_string/;
has _pokemon => ( is => 'rw', required => 1);
has _pokemon => ( is => 'rw', required => 1 );
sub species {
my $self = shift;
@ -26,27 +30,32 @@ sub species {
sub load_from_file {
my $class = shift;
my $file = shift;
my $file = shift;
$file = path($file);
die 'No such plain file' if !-f $file;
return $class->new(_pokemon => Rsaves::read_pk3_file($file));
return $class->new( _pokemon => Rsaves::read_pk3_file($file) );
}
sub export_file {
my $self = shift;
my $self = shift;
my $file = shift;
Rsaves::write_pk3_file($self->_pokemon, $file);
Rsaves::write_pk3_file( $self->_pokemon, $file );
}
sub export_raw {
my $self = shift;
return Rsaves::get_pk3_raw( $self->_pokemon );
}
sub empty {
my $class = shift;
return $class->new(_pokemon => Rsaves::read_pk3_raw("\0" x 0x80));
return $class->new( _pokemon => Rsaves::read_pk3_raw( "\0" x 0x80 ) );
}
sub copy {
my $self = shift;
my $self = shift;
my $object_to_copy_from = shift;
%{$self->_pokemon} = %{$object_to_copy_from->_pokemon};
%{ $self->_pokemon } = %{ $object_to_copy_from->_pokemon };
return $self;
}
@ -100,27 +109,27 @@ sub ivs {
}
sub evs {
my $self = shift;
my $arg = shift;
my $pokemon = $self->_pokemon;
my $self = shift;
my $arg = shift;
my $pokemon = $self->_pokemon;
my $substructure = $pokemon->{substructures}[2];
if (defined $arg) {
if (defined $arg->{HP}) {
if ( defined $arg ) {
if ( defined $arg->{HP} ) {
$substructure->{hp_ev} = $arg->{HP};
}
if (defined $arg->{Attack}) {
if ( defined $arg->{Attack} ) {
$substructure->{attack_ev} = $arg->{Attack};
}
if (defined $arg->{Defense}) {
if ( defined $arg->{Defense} ) {
$substructure->{defense_ev} = $arg->{Defense};
}
if (defined $arg->{Speed}) {
if ( defined $arg->{Speed} ) {
$substructure->{speed_ev} = $arg->{Speed};
}
if (defined $arg->{SpecialAttack}) {
if ( defined $arg->{SpecialAttack} ) {
$substructure->{special_attack_ev} = $arg->{SpecialAttack};
}
if (defined $arg->{SpecialDefense}) {
if ( defined $arg->{SpecialDefense} ) {
$substructure->{special_defense_ev} = $arg->{SpecialDefense};
}
}
@ -135,9 +144,9 @@ sub evs {
}
sub level {
my $self = shift;
my $arg = shift;
my $pokemon = $self->_pokemon;
my $self = shift;
my $arg = shift;
my $pokemon = $self->_pokemon;
return 0 if $self->species == 0;
my $growth_func = sub {
my $n = shift;
@ -149,8 +158,8 @@ sub level {
if ( defined $arg ) {
$pokemon->{substructures}[0]{experience} = $growth_func->($arg);
}
my $experience = $pokemon->{substructures}[0]{experience};
my $level = 1;
my $experience = $pokemon->{substructures}[0]{experience};
my $level = 1;
while ( $level <= 100 && int( $growth_func->($level) ) <= $experience ) {
$level++;
}
@ -384,4 +393,40 @@ sub get_front {
}
return "pokeemerald/graphics/pokemon/@{[lc($pokemon_name)]}/front.png";
}
sub backup {
my $self = shift;
my $db = GEmeTool::DB->connect;
my $data_dir = GEmeTool::Config->new->data_dir;
my $backup_dir = $data_dir->child('backups/pk3');
$backup_dir->mkpath;
my $nickname = Rsaves::translate_3rd_encoding( $self->nickname );
$nickname =~ s/\.\.//g;
$nickname =~ s/\///g;
my $contents = $self->export_raw;
my $digest = sha256_hex($contents);
my $file = $backup_dir->child(
$nickname . '-' . $self->pokemon_name . '-' . $digest . '.pk3' )
->absolute;
$file->spew_raw($contents);
my $uuid = create_uuid_string();
my $query_check_exists = <<'EOF';
SELECT file FROM backups_pk3
WHERE file = ?
EOF
if ( $db->selectrow_hashref( $query_check_exists, undef, $file ) ) {
$db->do( <<'EOF', undef, $file );
UPDATE backups_pk3
SET date=datetime('now')
WHERE file = ?;
EOF
return;
}
my $query = <<'EOF';
INSERT INTO backups_pk3 (uuid, date, nickname, pokemon_name, file)
VALUES (?, datetime('now'), ?, ?, ?);
EOF
$db->do( $query, {}, $uuid, $self->nickname, $self->pokemon_name, $file );
}
1;

View File

@ -17,6 +17,7 @@ use Rsaves::Constants::Emerald::Species;
use Rsaves::Constants::Emerald::Natures;
use GEmeTool::Save::Pokemon;
use GEmeTool::Options;
use GEmeTool::DB;
Glib::Object::Introspection->setup(
basename => 'Gtk',
@ -98,58 +99,120 @@ sub draw {
}
);
$grid->attach( $canvas, 0, 0, 2, 2 );
my @species = (@Rsaves::Constants::Emerald::Species::SPECIES);
my $string_list = Gtk4::StringList->new( [@species] );
my $save_button = Gtk4::Button->new_with_label('Save changes');
my $delete_button = Gtk4::Button->new_with_label('Delete pokemon');
my $import_button = Gtk4::Button->new_with_label('Import .pk3');
my $export_button = Gtk4::Button->new_with_label('Export .pk3');
my @species = (@Rsaves::Constants::Emerald::Species::SPECIES);
my $string_list = Gtk4::StringList->new( [@species] );
my $restore_button = Gtk4::Button->new_with_label('Restore from backup');
my $save_button = Gtk4::Button->new_with_label('Save changes');
my $delete_button = Gtk4::Button->new_with_label('Delete pokemon');
my $import_button = Gtk4::Button->new_with_label('Import .pk3');
my $export_button = Gtk4::Button->new_with_label('Export .pk3');
$self->_selected_species( $pokemon->species );
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');
my $box_ev = Gtk4::Box->new('vertical', 1);
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'));
$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 );
$self->recalculate_ivs;
$self->recalculate_evs;
for my $func ( @{ $self->_save_callbacks } ) {
$func->();
}
$self->draw;
}
);
$delete_button->signal_connect(clicked => sub {
$self->delete;
});
$import_button->signal_connect(clicked => sub {
$self->activate_import_pk3;
});
$export_button->signal_connect(clicked => sub {
$self->activate_export_pk3;
});
$delete_button->signal_connect(
clicked => sub {
$self->delete;
}
);
$import_button->signal_connect(
clicked => sub {
$self->activate_import_pk3;
}
);
$export_button->signal_connect(
clicked => sub {
$self->activate_export_pk3;
}
);
$restore_button->signal_connect(
clicked => sub {
$self->activate_restore;
}
);
$self->draw_dropdown_pokemon_list($box_right_image);
my $box_label_iv = Gtk4::Box->new('horizontal', 10);
$box_label_iv->append(Gtk4::Label->new('Select IV'));
my $box_label_iv = Gtk4::Box->new( 'horizontal', 10 );
$box_label_iv->append( Gtk4::Label->new('Select IV') );
$box_right_image->append($box_label_iv);
$self->create_select_ivs($box_right_image);
$self->create_modify_personality($grid);
$grid->attach( $save_button, 4, 7, 1, 1 );
$grid->attach( $export_button, 3, 7, 1, 1 );
$grid->attach( $delete_button, 2, 7, 1, 1 );
$grid->attach( $import_button, 1, 7, 1, 1 );
$grid->attach( $save_button, 4, 7, 1, 1 );
$grid->attach( $export_button, 3, 7, 1, 1 );
$grid->attach( $delete_button, 2, 7, 1, 1 );
$grid->attach( $import_button, 1, 7, 1, 1 );
$grid->attach( $restore_button, 0, 7, 1, 1 );
$window->set_child($grid);
}
sub activate_restore {
my $self = shift;
my $window = Gtk4::Window->new;
$window->set_title('Restore from backup');
my $scroll = Gtk4::ScrolledWindow->new;
my $grid = Gtk4::Grid->new;
my $backups = $self->get_backups;
my $i = 0;
for my $backup (@$backups) {
my $nickname = Rsaves::translate_3rd_encoding( $backup->{nickname} );
my $pokemon_name = $backup->{pokemon_name};
my $file = $backup->{file};
my $button =
Gtk4::Button->new_with_label( $nickname . '-' . $pokemon_name );
$button->signal_connect(
clicked => sub {
$self->activate_save;
$self->pokemon->backup;
$self->pokemon->copy(
GEmeTool::Save::Pokemon->load_from_file($file) );
$self->draw;
$window->close;
}
);
$grid->attach( $button, $i % 6, int( $i / 6 ), 1, 1 );
$i++;
}
$window->set_child($grid);
$window->present;
}
sub get_backups {
my $self = shift;
my $db = GEmeTool::DB->connect;
my $query = <<'EOF';
SELECT nickname, pokemon_name, file
FROM backups_pk3
ORDER BY date DESC;
EOF
my $result = $db->selectall_arrayref( $query, { Slice => {} } );
return $result;
}
sub activate_save {
my $self = shift;
my $pokemon = $self->pokemon;
$pokemon->backup;
$pokemon->species( $self->_selected_species );
$pokemon->personality( $self->_current_personality );
$self->recalculate_ivs;
$self->recalculate_evs;
for my $func ( @{ $self->_save_callbacks } ) {
$func->();
}
$self->draw;
}
sub activate_export_pk3 {
my $self = shift;
my $win = $self->_win;
@ -159,7 +222,10 @@ sub activate_export_pk3 {
if ( defined $last_dir && -d $last_dir ) {
my $curdir = Glib::IO::File::new_for_path($last_dir);
$dialog->set_initial_folder($curdir);
$dialog->set_initial_name( Rsaves::translate_3rd_encoding($self->pokemon->nickname) . '-' . $self->pokemon->pokemon_name . '.pk3');
$dialog->set_initial_name(
Rsaves::translate_3rd_encoding( $self->pokemon->nickname ) . '-'
. $self->pokemon->pokemon_name
. '.pk3' );
}
$dialog->save(
$win, undef,
@ -178,7 +244,8 @@ sub activate_export_pk3 {
sub delete {
my $self = shift;
$self->pokemon->copy(GEmeTool::Save::Pokemon->empty);
$self->pokemon->backup;
$self->pokemon->copy( GEmeTool::Save::Pokemon->empty );
$self->draw;
}
@ -187,7 +254,7 @@ sub activate_import_pk3 {
my $cancellable = Glib::IO::Cancellable->new;
my $dialog = Gtk4::FileDialog->new;
my $win = $self->_win;
my $options = GEmeTool::Options->new;
my $options = GEmeTool::Options->new;
my $last_dir = $options->get_last_dir_open;
if ( defined $last_dir && -d $last_dir ) {
my $curdir = Glib::IO::File::new_for_path($last_dir);
@ -205,47 +272,53 @@ sub activate_import_pk3 {
return if !defined $file;
$file = path( $file->get_path );
$options->set_last_dir_open( $file->parent . '' );
$self->pokemon->copy(GEmeTool::Save::Pokemon->load_from_file($file));
$self->activate_save;
$self->pokemon->backup;
$self->pokemon->copy(
GEmeTool::Save::Pokemon->load_from_file($file) );
$self->draw;
}
);
}
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 $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);
$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;
$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);
}
if ($text > 100) {
$text = 100;
}
if ($text < 2) {
$text = 2;
}
$pokemon->level($text);
});
);
$box->append($box_level);
}
sub recalculate_evs {
my $self = shift;
my $self = shift;
my $pokemon = $self->pokemon;
for my $key ( @stats ) {
for my $key (@stats) {
my $text = $self->_ev_buffers->{$key}->get_text;
next if $text !~ /^[0-9]+$/;
if ($text > 255) {
if ( $text > 255 ) {
$text = 255;
}
if ($text < 0) {
if ( $text < 0 ) {
$text = 0;
}
$pokemon->evs(
@ -257,15 +330,15 @@ sub recalculate_evs {
}
sub recalculate_ivs {
my $self = shift;
my $self = shift;
my $pokemon = $self->pokemon;
for my $key ( @stats ) {
for my $key (@stats) {
my $text = $self->_iv_buffers->{$key}->get_text;
next if $text !~ /^[0-9]+$/;
if ($text > 31) {
if ( $text > 31 ) {
$text = 31;
}
if ($text < 0) {
if ( $text < 0 ) {
$text = 0;
}
$pokemon->ivs(