GEmeTool/lib/GEmeTool/Save/Pokemon.pm

437 lines
12 KiB
Perl

package GEmeTool::Save::Pokemon;
use v5.16.3;
use strict;
use warnings;
use Data::Dumper;
use Moo;
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 Digest::SHA qw/sha256_hex/;
use UUID::URandom qw/create_uuid_string/;
has _pokemon => ( is => 'rw', required => 1 );
sub species {
my $self = shift;
my $arg = shift;
my $pokemon = $self->_pokemon;
my $substruct_0 = $pokemon->{substructures}[0];
if ( defined $arg ) {
$substruct_0->{species} = $arg;
}
return $substruct_0->{species};
}
sub load_from_file {
my $class = shift;
my $file = shift;
$file = path($file);
die 'No such plain file' if !-f $file;
return $class->new( _pokemon => Rsaves::read_pk3_file($file) );
}
sub export_file {
my $self = shift;
my $file = shift;
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 ) );
}
sub copy {
my $self = shift;
my $object_to_copy_from = shift;
%{ $self->_pokemon } = %{ $object_to_copy_from->_pokemon };
return $self;
}
sub ivs {
my $self = shift;
my $arg = shift;
print Data::Dumper::Dumper $self->_pokemon;
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;
my $pokemon = $self->_pokemon;
return 0 if $self->species == 0;
my $growth_func = sub {
my $n = shift;
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 ) {
$level++;
}
$level -= 1;
return $level;
}
sub growth_function {
my $self = shift;
my $growth = $self->growth;
if ( $growth eq 'GROWTH_FAST' ) {
return \&_exp_fast;
}
if ( $growth eq 'GROWTH_MEDIUM_FAST' ) {
return \&_exp_medium_fast;
}
if ( $growth eq 'GROWTH_MEDIUM_SLOW' ) {
return \&_exp_medium_slow;
}
if ( $growth eq 'GROWTH_SLOW' ) {
return \&_exp_slow;
}
if ( $growth eq 'GROWTH_ERRATIC' ) {
return \&_exp_erratic;
}
if ( $growth eq 'GROWTH_FLUCTUATING' ) {
return \&_exp_fluctuating;
}
}
sub gender_ratio {
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 $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 $gender_ratio = $self->gender_ratio;
if ( $gender_ratio == 0 ) {
return 0;
}
if ( $gender_ratio == 254 ) {
return 1;
}
if ( $gender_ratio == 255 ) {
return 2;
}
if ( $gender_ratio <= ( $personality & 0xff ) ) {
return 0;
}
return 1;
}
sub personality {
my $self = shift;
my $arg = shift;
my $pokemon = $self->_pokemon;
if ( defined $arg ) {
$self->_pokemon->{personality} = $arg;
}
return $pokemon->{personality};
}
sub growth {
my $self = shift;
my $pokemon_name = $self->pokemon_name;
my %pokemon_data = %Rsaves::Constants::Emerald::SpeciesData::SPECIES_DATA;
my $data = $pokemon_data{$pokemon_name};
return $data->{growth_rate};
}
sub _square {
return $_[0]**2;
}
sub _cube {
return $_[0]**3;
}
sub _exp_slow {
my $n = shift;
return ( 5 * _cube($n) ) / 4;
}
sub _exp_fast {
my $n = shift;
return ( 4 * _cube($n) ) / 5;
}
sub _exp_medium_fast {
return _cube( $_[0] );
}
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 );
return $return;
}
sub _exp_erratic {
my $n = shift;
if ( $n <= 50 ) {
return ( ( 100 - $n ) * _cube($n) / 50 );
}
if ( $n <= 68 ) {
return ( ( 150 - $n ) * _cube($n) / 100 );
}
if ( $n <= 98 ) {
return ( ( 1911 - 10 * $n ) / 3 * _cube($n) / 500 );
}
return ( ( 160 - $n ) * _cube($n) / 100 );
}
sub _exp_fluctuating {
my $n = shift;
if ( $n <= 15 ) {
return ( ( ( $n + 1 ) / 3 + 24 ) * _cube($n) / 50 );
}
if ( $n <= 36 ) {
return ( ( $n + 14 ) * _cube($n) / 50 );
}
return ( ( ( $n / 2 ) + 32 ) * _cube($n) / 50 );
}
sub pokemon_name {
my $self = shift;
return $Rsaves::Constants::Emerald::Species::SPECIES[ $self->species ];
}
sub nickname {
my $self = shift;
my $arg = shift;
if ( defined $arg ) {
die "Invalid nickname" if length $arg != 10;
$self->_pokemon->{nickname} = $arg;
}
return $self->_pokemon->{nickname};
}
sub get_icon {
my $self = shift;
my $pokemon_name =
$Rsaves::Constants::Emerald::Species::SPECIES[ $self->species ];
if ( lc($pokemon_name) eq 'unown' ) {
return "pokeemerald/graphics/pokemon/@{[lc($pokemon_name)]}/z/icon.png";
}
return "pokeemerald/graphics/pokemon/@{[lc($pokemon_name)]}/icon.png";
}
sub generate_personality {
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 ) ) )
{
$should_search_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 ) )
{
die "Incorrect nature $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 ) ) )
{
next;
}
if ( $should_search_gender && $self->gender($i) != $target_gender ) {
next;
}
$personality = $i;
last;
}
if ( !defined $personality ) {
warn "Could not find personality combination, this is a bug.";
}
return $personality;
}
sub otid {
my $self = shift;
my $arg = shift;
my $pokemon = $self->_pokemon;
if ( defined $arg ) {
$pokemon->{otid} = $arg;
}
return $pokemon->{otid};
}
sub get_front {
my $self = shift;
my $pokemon_name =
$Rsaves::Constants::Emerald::Species::SPECIES[ $self->species ];
if ( Rsaves::pokemon_is_shiny( $self->_pokemon ) ) {
return "resources/shiny/@{[lc($pokemon_name)]}.png";
}
if ( lc($pokemon_name) eq 'castform' ) {
return
"pokeemerald/graphics/pokemon/@{[lc($pokemon_name)]}/normal/front.png";
}
if ( lc($pokemon_name) eq 'unown' ) {
return
"pokeemerald/graphics/pokemon/@{[lc($pokemon_name)]}/z/front.png";
}
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;