package Games::SMTNocturne::Demons; use strict; use warnings; use Exporter 'import'; our @EXPORT_OK = qw(demon demons_of_type all_demons fuse fusions_for); use Games::SMTNocturne::Demons::Demon; use Games::SMTNocturne::Demons::Fusion; use Games::SMTNocturne::Demons::FusionChart; sub demon { my ($demon) = @_; return Games::SMTNocturne::Demons::Demon->from_name($demon); } sub demons_of_type { my ($type) = @_; return Games::SMTNocturne::Demons::Demon->from_type($type); } sub all_demons { return Games::SMTNocturne::Demons::Demon->all_demons; } sub fuse { my ($demon1, $demon2, $options) = @_; $options = { %{ $options || {} } }; $demon1 = demon($demon1) unless ref($demon1); $demon2 = demon($demon2) unless ref($demon2); if ($options->{sacrifice}) { $options->{sacrifice} = demon($options->{sacrifice}) unless ref($options->{sacrifice}); } if (!$options->{basic}) { if (my $demon = _try_special_fusion($demon1, $demon2, $options)) { # XXX this is the wrong place for this, but not sure how to do # it better return if $demon->type eq 'Fiend' && ($demon1->type eq 'Fiend' || $demon2->type eq 'Fiend'); return $demon; } else { $options->{fusion_type} = 'normal'; } } if ($demon1->type eq 'Element' && $demon2->type eq 'Element') { return _fuse_mitama($demon1, $demon2, $options); } elsif ($demon1->type eq 'Element' || $demon2->type eq 'Element') { return _element_fusion( ($demon1->type eq 'Element' ? ($demon1, $demon2) : ($demon2, $demon1)), $options ); } elsif ($demon1->type eq 'Mitama' && $demon2->type eq 'Mitama') { return; } elsif ($demon1->type eq 'Mitama' || $demon2->type eq 'Mitama') { return _mitama_fusion( ($demon1->type eq 'Mitama' ? ($demon1, $demon2) : ($demon2, $demon1)), $options ); } elsif ($demon1->type eq $demon2->type) { return _fuse_element($demon1, $demon2, $options); } else { return _normal_fusion($demon1, $demon2, $options); } } sub fusions_for { my ($demon, $options) = @_; $demon = demon($demon) unless ref($demon); my @fusions; my %seen; for my $types (Games::SMTNocturne::Demons::FusionChart::unfuse($demon->type)) { my ($type1, $type2) = @$types; for my $demon1 (Games::SMTNocturne::Demons::Demon->from_type($type1)) { next if defined $options->{max_level} && $options->{max_level} < $demon1->level; for my $demon2 (Games::SMTNocturne::Demons::Demon->from_type($type2)) { next if defined $options->{max_level} && $options->{max_level} < $demon2->level; push @fusions, [ $options, $demon1, $demon2 ] if (fuse($demon1, $demon2, $options) || '') eq $demon; } } } my $special = Games::SMTNocturne::Demons::FusionChart::special_fusion_for( $demon->name ); my @special_fusions; if ($special) { for my $key (qw(demon1 demon2 demon3 target sacrifice)) { next unless $special->{$key}; if (my $name = $special->{$key}{name}) { $special->{$key} = [ demon($name) ]; } elsif (my $type = $special->{$key}{type}) { my @types = ref($type) ? (@$type) : ($type); $special->{$key} = [ map { Games::SMTNocturne::Demons::Demon->from_type($_) } @types ]; } $special->{$key} = [ grep { $_->level <= $options->{max_level} } @{ $special->{$key} } ] if $key ne 'target' && defined $options->{max_level}; } if ($special->{demon3}) { for my $demon1 (@{ $special->{demon1} }) { for my $demon2 (@{ $special->{demon2} }) { for my $demon3 (@{ $special->{demon3} }) { push @special_fusions, [ $options, $demon1, $demon2, $demon3 ]; push @special_fusions, [ $options, $demon1, $demon3, $demon2 ]; push @special_fusions, [ $options, $demon2, $demon3, $demon1 ]; } } } } elsif ($special->{demon2}) { for my $demon1 (@{ $special->{demon1} }) { for my $demon2 (@{ $special->{demon2} }) { push @special_fusions, [ $options, $demon1, $demon2 ]; } } } elsif ($special->{demon1}) { if ($special->{target}) { my @target_fusions = map { $_->raw } map { fusions_for($_, $options) } @{ $special->{target} }; push @special_fusions, grep { my $fusion = $_; grep { $_ eq $fusion->[0] || $_ eq $fusion->[1] } @{ $special->{demon1} } } @target_fusions; } else { die "???"; } } else { if ($special->{target}) { my @new_special = map { $_->raw } map { fusions_for($_, $options) } @{ $special->{target} }; if ($demon->type eq 'Fiend') { @new_special = grep { $_->[1]->type ne 'Fiend' && $_->[2]->type ne 'Fiend' } @new_special; } push @special_fusions, @new_special; } else { die "???"; } } if ($special->{sacrifice}) { @special_fusions = map { my $sac = $_; map { [ @$_, $sac ] } @special_fusions } @{ $special->{sacrifice} }; } if ($special->{deathstone}) { push @$_, '' for @special_fusions; } if ($special->{kagutsuchi}) { push @$_, $special->{kagutsuchi} for @special_fusions; } } return map { Games::SMTNocturne::Demons::Fusion->new(@$_) } @fusions, @special_fusions; } sub _try_special_fusion { my ($demon1, $demon2, $options) = @_; my $fused = Games::SMTNocturne::Demons::FusionChart::special_fusion( $demon1, $demon2, $options ); return unless $fused; my $demon = demon($fused); my %bosses = map { $_ => 1 } @{ $options->{bosses} || [] }; return if $demon->boss && !$bosses{$demon->name}; return $demon; } sub _fuse_mitama { my ($element1, $element2) = @_; my $mitama = Games::SMTNocturne::Demons::FusionChart::fuse_mitama( $element1->name, $element2->name ); return unless $mitama; return demon($mitama); } sub _element_fusion { my ($element, $demon, $options) = @_; my $direction = Games::SMTNocturne::Demons::FusionChart::element_fusion( $demon->type, $element->name ); return unless $direction; return Games::SMTNocturne::Demons::Demon->from_fusion_stats({ type => $demon->type, level => $demon->level, offset => $direction, %{ $options || {} }, }); } sub _mitama_fusion { my ($mitama, $demon) = @_; return $demon; } sub _fuse_element { my ($demon1, $demon2) = @_; my $element = Games::SMTNocturne::Demons::FusionChart::fuse_element( $demon1->type ); return unless $element; return demon($element); } sub _normal_fusion { my ($demon1, $demon2, $options) = @_; my $new_type = Games::SMTNocturne::Demons::FusionChart::fuse( $demon1->type, $demon2->type ); return unless $new_type; my $new_level = ($demon1->level + $demon2->level) / 2 + 1; return Games::SMTNocturne::Demons::Demon->from_fusion_stats({ type => $new_type, level => $new_level, %{ $options || {} }, }); } 1;