#=============================================================================== # Instances of this class are individual Pokémon. # The player's party Pokémon are stored in the array $Trainer.party. #=============================================================================== class Pokemon attr_accessor :spriteform_body attr_accessor :spriteform_head # @return [Symbol] this Pokémon's species attr_reader :species # If defined, this Pokémon's form will be this value even if a MultipleForms # handler tries to say otherwise. # @return [Integer, nil] this Pokémon's formspriteform attr_accessor :forced_form # If defined, is the time (in Integer form) when this Pokémon's form was set. # @return [Integer, nil] the time this Pokémon's form was set attr_accessor :time_form_set # @return [Integer] the current experience points attr_reader :exp attr_accessor :exp_when_fused_head attr_accessor :exp_when_fused_body attr_accessor :exp_gained_since_fused attr_accessor :hat attr_accessor :hat_x attr_accessor :hat_y # @return [Integer] the number of steps until this Pokémon hatches, 0 if this Pokémon is not an egg attr_accessor :steps_to_hatch # @return [Integer] the current HP attr_reader :hp # @return [Symbol] this Pokémon's current status (see GameData::Status) attr_reader :status # @return [Integer] sleep count / toxic flag / 0: # sleep (number of rounds before waking up), toxic (0 = regular poison, 1 = toxic) attr_accessor :statusCount # This Pokémon's shininess (true, false, nil). Is recalculated if made nil. # @param value [Boolean, nil] whether this Pokémon is shiny #attr_writer :shiny attr_accessor :glitter attr_accessor :head_shiny attr_accessor :body_shiny attr_accessor :debug_shiny attr_accessor :natural_shiny # The index of this Pokémon's ability (0, 1 are natural abilities, 2+ are # hidden abilities)as defined for its species/form. An ability may not be # defined at this index. Is recalculated (as 0 or 1) if made nil. # @param value [Integer, nil] forced ability index (nil if none is set) attr_writer :ability_index attr_accessor :body_original_ability_index attr_accessor :head_original_ability_index # @return [Array] the moves known by this Pokémon attr_accessor :moves # @return [Array] All the moves ever learned by this Pokémon attr_accessor :learned_moves # @return [Array] the IDs of moves known by this Pokémon when it was obtained attr_accessor :first_moves # @return [Array] an array of ribbons owned by this Pokémon attr_accessor :ribbons # @return [Integer] contest stats attr_accessor :cool, :beauty, :cute, :smart, :tough, :sheen # @return [Integer] the Pokérus strain and infection time attr_accessor :pokerus # @return [Integer] this Pokémon's current happiness (an integer between 0 and 255) attr_accessor :happiness # @return [Symbol] the item ID of the Poké Ball this Pokémon is in attr_accessor :poke_ball # @return [Integer] this Pokémon's markings, one bit per marking attr_accessor :markings # @return [Hash] a hash of IV values for HP, Atk, Def, Speed, Sp. Atk and Sp. Def attr_accessor :iv # An array of booleans indicating whether a stat is made to have maximum IVs # (for Hyper Training). Set like @ivMaxed[:ATTACK] = true # @return [Hash] a hash of booleans that max each IV value attr_accessor :ivMaxed # @return [Hash] this Pokémon's effort values attr_accessor :ev # @return [Integer] calculated stats attr_reader :totalhp, :attack, :defense, :spatk, :spdef, :speed # @return [Owner] this Pokémon's owner attr_reader :owner # @return [Integer] the manner this Pokémon was obtained: # 0 (met), 1 (as egg), 2 (traded), 4 (fateful encounter) attr_accessor :obtain_method # @return [Integer] the ID of the map this Pokémon was obtained in attr_accessor :obtain_map # Describes the manner this Pokémon was obtained. If left undefined, # the obtain map's name is used. # @return [String] the obtain text attr_accessor :obtain_text # @return [Integer] the level of this Pokémon when it was obtained attr_accessor :obtain_level # If this Pokémon hatched from an egg, returns the map ID where the hatching happened. # Otherwise returns 0. # @return [Integer] the map ID where egg was hatched (0 by default) attr_accessor :hatched_map # Another Pokémon which has been fused with this Pokémon (or nil if there is none). # Currently only used by Kyurem, to record a fused Reshiram or Zekrom. # @return [Pokemon, nil] the Pokémon fused into this one (nil if there is none) attr_accessor :fused # @return [Integer] this Pokémon's personal ID attr_accessor :personalID attr_accessor :hiddenPowerType attr_accessor :sprite_scale #the size attribute for scaling the sprite (used only for gourgeist/pumpkaboo) attr_accessor :size_category #the size attribute for scaling the sprite (used only for gourgeist/pumpkaboo) attr_accessor :force_disobey # Max total IVs IV_STAT_LIMIT = 31 # Max total EVs EV_LIMIT = 510 # Max EVs that a single stat can have EV_STAT_LIMIT = 252 # Maximum length a Pokémon's nickname can be MAX_NAME_SIZE = 16 # Maximum number of moves a Pokémon can know at once MAX_MOVES = 4 S_CHANCE_VALIDATOR = 16 def print_all_attributes echoln("Spriteform Body: #{@spriteform_body}") echoln("Spriteform Head: #{@spriteform_head}") echoln("Species: #{@species}") echoln("Forced Form: #{@forced_form}") echoln("Time Form Set: #{@time_form_set}") echoln("Experience: #{@exp}") echoln("EXP When Fused Head: #{@exp_when_fused_head}") echoln("EXP When Fused Body: #{@exp_when_fused_body}") echoln("EXP Gained Since Fused: #{@exp_gained_since_fused}") echoln("Hat: #{@hat}") echoln("Hat X: #{@hat_x}") echoln("Hat Y: #{@hat_y}") echoln("Steps to Hatch: #{@steps_to_hatch}") echoln("HP: #{@hp}") echoln("Status: #{@status}") echoln("Status Count: #{@statusCount}") echoln("Glitter: #{@glitter}") echoln("Head Shiny: #{@head_shiny}") echoln("Body Shiny: #{@body_shiny}") echoln("Debug Shiny: #{@debug_shiny}") echoln("Natural Shiny: #{@natural_shiny}") echoln("Calculated ability: #{@ability}") echoln("Abilities hash: #{getAbilityList()}") echoln("Ability Index: #{@ability_index}") echoln("Body Original Ability Index: #{@body_original_ability_index}") echoln("Head Original Ability Index: #{@head_original_ability_index}") move1, move2, move3, move4 = "", "", "", "" move1 = @moves[0].id if @moves[0] move2 = @moves[1].id if @moves[1] move3 = @moves[2].id if @moves[2] move4 = @moves[3].id if @moves[3] echoln("Moves: #{move1} #{move2} #{move3} #{move4}") echoln("First Moves: #{@first_moves}") echoln("Ribbons: #{@ribbons}") echoln("Cool: #{@cool}, Beauty: #{@beauty}, Cute: #{@cute}, Smart: #{@smart}, Tough: #{@tough}, Sheen: #{@sheen}") echoln("Pokerus: #{@pokerus}") echoln("Happiness: #{@happiness}") echoln("Poke Ball: #{@poke_ball}") echoln("Markings: #{@markings}") echoln("IV: #{@iv}") echoln("IV Maxed: #{@ivMaxed}") echoln("EV: #{@ev}") echoln("Total HP: #{@totalhp}, Attack: #{@attack}, Defense: #{@defense}, Sp. Attack: #{@spatk}, Sp. Defense: #{@spdef}, Speed: #{@speed}") echoln("Owner: #{@owner}") echoln("Obtain Method: #{@obtain_method}") echoln("Obtain Map: #{@obtain_map}") echoln("Obtain Text: #{@obtain_text}") echoln("Obtain Level: #{@obtain_level}") echoln("Hatched Map: #{@hatched_map}") echoln("Fused: #{@fused}") echoln("Personal ID: #{@personalID}") echoln("Hidden Power Type: #{@hiddenPowerType}") echoln("Scale: #{sprite_scale}") # Add other attribute print statements here end def self.play_cry(species, form = 0, volume = 90, pitch = 100) GameData::Species.play_cry_from_species(species, form, volume, pitch) end def play_cry(volume = 90, pitch = nil) GameData::Species.play_cry_from_pokemon(self, volume, pitch) end def inspect str = super.chop str << format(' %s Lv.%s>', @species, @level.to_s || '???') return str end def species_data @species_data = GameData::Species.get(@species) return @species_data end def id_number return species_data.id_number end #============================================================================= # Species and form #============================================================================= def hiddenPower=(type) @hiddenPowerType = type end # Changes the Pokémon's species and re-calculates its statistics. # @param species_id [Integer] id of the species to change this Pokémon to def species=(species_id) new_species_data = GameData::Species.get(species_id) return if @species == new_species_data.species @species = new_species_data.species @form = new_species_data.form if new_species_data.form != 0 @forced_form = nil @level = nil # In case growth rate is different for the new species @ability = nil calc_stats end def sprite_scale() @sprite_scale = 1 if !@sprite_scale return @sprite_scale end def sprite_scale=(scale) @sprite_scale = scale end def size_category() @size_category = :AVERAGE if !@size_category return @size_category end def size_category=(category) @size_category = category end # @param check_species [Integer, Symbol, String] id of the species to check for # @return [Boolean] whether this Pokémon is of the specified species def isSpecies?(check_species) return @species == check_species || (GameData::Species.exists?(check_species) && @species == GameData::Species.get(check_species).species) end def hasBodyOf?(check_species) if !self.isFusion? return isSpecies?(check_species) end bodySpecies = getBodyID(species) checkSpeciesId = getID(nil, check_species) return bodySpecies == checkSpeciesId end def hasHeadOf?(check_species) if !self.isFusion? return isSpecies?(check_species) end headSpecies = getHeadID(species) checkSpeciesId = getID(nil, check_species) return headSpecies == checkSpeciesId end def head_id() return get_head_id_from_symbol(@species) end def body_id() return get_body_id_from_symbol(@species) end def shiny=(value) @shiny = value if value && Settings::SHINY_POKEMON_CHANCE != S_CHANCE_VALIDATOR @debug_shiny = true end end def naturalShiny? return @natural_shiny end def debugShiny? return !@natural_shiny || @debug_shiny end def bodyShiny? return @body_shiny end def headShiny? return @head_shiny end def isFusionOf(check_species) return hasBodyOf?(check_species) || hasHeadOf?(check_species) end def dexNum return species_data.id_number end def isSelfFusion? return isFusion? && getHeadID(species) == getBodyID(species) end def isFusion? return species_data.id_number > NB_POKEMON && !self.isTripleFusion? end def isTripleFusion? return species_data.id_number >= Settings::ZAPMOLCUNO_NB end def form return @forced_form if !@forced_form.nil? return @form if $game_temp.in_battle calc_form = MultipleForms.call("getForm", self) self.form = calc_form if calc_form != nil && calc_form != @form return @form end def changeFormSpecies(oldForm, newForm) is_already_old_form = self.isFusionOf(oldForm) #A 466 is_already_new_form = self.isFusionOf(newForm) #P #reverse the fusion if it's a meloA and meloP fusion # There's probably a smarter way to do this but laziness lol if is_already_old_form && is_already_new_form body_id = self.species_data.get_body_species() body_species = GameData::Species.get(body_id) if body_species == oldForm changeSpeciesSpecific(self, getFusedPokemonIdFromSymbols(newForm, oldForm)) else changeSpeciesSpecific(self, getFusedPokemonIdFromSymbols(oldForm, newForm)) end else echoln "changing species...." changeSpecies(self, oldForm, newForm) if is_already_old_form changeSpecies(self, newForm, oldForm) if is_already_new_form end calc_stats end def changeSpecies(pokemon, speciesToReplace, newSpecies) if pokemon.isFusion?() replaceFusionSpecies(pokemon, speciesToReplace, newSpecies) else changeSpeciesSpecific(pokemon, newSpecies) end $Trainer.pokedex.set_seen(pokemon.species) $Trainer.pokedex.set_owned(pokemon.species) end def form_simple return @forced_form || @form end def form=(value) # oldForm = @form # @form = value # @ability = nil # MultipleForms.call("onSetForm", self, value, oldForm) # calc_stats # $Trainer.pokedex.register(self) end # The same as def form=, but yields to a given block in the middle so that a # message about the form changing can be shown before calling "onSetForm" # which may have its own messages, e.g. learning a move. def setForm(value) oldForm = @form @form = value @ability = nil yield if block_given? MultipleForms.call("onSetForm", self, value, oldForm) calc_stats $Trainer.pokedex.register(self) end def form_simple=(value) @form = value calc_stats end #============================================================================= # Level #============================================================================= # @return [Integer] this Pokémon's level def level @level = growth_rate.level_from_exp(@exp) if !@level return @level end # Sets this Pokémon's level. The given level must be between 1 and the # maximum level (defined in {GameData::GrowthRate}). # @param value [Integer] new level (between 1 and the maximum level) def level=(value) if value < 1 || value > GameData::GrowthRate.max_level raise ArgumentError.new(_INTL("The level number ({1}) is invalid.", value)) end @exp = growth_rate.minimum_exp_for_level(value) @level = value end # Sets this Pokémon's Exp. Points. # @param value [Integer] new experience points def exp=(value) @exp = value @level = nil end # @return [Boolean] whether this Pokémon is an egg def egg? return @steps_to_hatch > 0 end # @return [GameData::GrowthRate] this Pokémon's growth rate def growth_rate return GameData::GrowthRate.get(species_data.growth_rate) end # @return [Integer] this Pokémon's base Experience value def base_exp return species_data.base_exp end # @return [Float] a number between 0 and 1 indicating how much of the current level's # Exp this Pokémon has def exp_fraction lvl = self.level return 0.0 if lvl >= GameData::GrowthRate.max_level g_rate = growth_rate start_exp = g_rate.minimum_exp_for_level(lvl) end_exp = g_rate.minimum_exp_for_level(lvl + 1) return (@exp - start_exp).to_f / (end_exp - start_exp) end #============================================================================= # Status #============================================================================= # Sets the Pokémon's health. # @param value [Integer] new HP value def hp=(value) @hp = value.clamp(0, @totalhp) heal_status if @hp == 0 checkHPRelatedFormChange() end # Sets this Pokémon's status. See {GameData::Status} for all possible status effects. # @param value [Integer, Symbol, String] status to set def status=(value) return if !able? new_status = GameData::Status.try_get(value) if !new_status raise ArgumentError, _INTL('Attempted to set {1} as Pokémon status', value.class.name) end @status = new_status.id end # @return [Boolean] whether the Pokémon is not fainted and not an egg def able? return !egg? && @hp > 0 end # @return [Boolean] whether the Pokémon is fainted def fainted? return !egg? && @hp <= 0 end # Heals all HP of this Pokémon. def heal_HP return if egg? @hp = @totalhp checkHPRelatedFormChange() end # Heals the status problem of this Pokémon. def heal_status return if egg? @status = :NONE @statusCount = 0 end # Restores all PP of this Pokémon. If a move index is given, restores the PP # of the move in that index. # @param move_index [Integer] index of the move to heal (-1 if all moves # should be healed) def heal_PP(move_index = -1) return if egg? if move_index >= 0 @moves[move_index].pp = @moves[move_index].total_pp else @moves.each { |m| m.pp = m.total_pp } end end # Heals all HP, PP, and status problems of this Pokémon. def heal return if egg? heal_HP heal_status heal_PP end #============================================================================= # Types #============================================================================= # @return [Symbol] this Pokémon's first type def type1 if @ability == :MULTITYPE && species_data.type1 == :NORMAL return getHeldPlateType() end return @type1 if @type1 return species_data.type1 end # @return [Symbol] this Pokémon's second type, or the first type if none is defined def type2 if @ability == :MULTITYPE && species_data.type2 == :NORMAL return getHeldPlateType() end sp_data = species_data return sp_data.type2 || sp_data.type1 end def type1=(value) @type1 = value end def type2=(value) @type2 = value end # @return [Array] an array of this Pokémon's types def types sp_data = species_data ret = [sp_data.type1] ret.push(sp_data.type2) if sp_data.type2 && sp_data.type2 != sp_data.type1 return ret end # @param type [Symbol, String, Integer] type to check # @return [Boolean] whether this Pokémon has the specified type def hasType?(type) type = GameData::Type.get(type).id return self.types.include?(type) end def getHeldPlateType() return getArceusPlateType(@item) end #============================================================================= # Gender #============================================================================= # @return [0, 1, 2] this Pokémon's gender (0 = male, 1 = female, 2 = genderless) def gender if !@gender gender_ratio = species_data.gender_ratio case gender_ratio when :AlwaysMale then @gender = 0 when :AlwaysFemale then @gender = 1 when :Genderless then @gender = 2 else female_chance = GameData::GenderRatio.get(gender_ratio).female_chance @gender = ((@personalID & 0xFF) < female_chance) ? 1 : 0 end end return @gender end # Sets this Pokémon's gender to a particular gender (if possible). # @param value [0, 1] new gender (0 = male, 1 = female) def gender=(value) return if singleGendered? @gender = value if value.nil? || value == 0 || value == 1 end # Makes this Pokémon male. def makeMale @gender = 0 end # Makes this Pokémon female. def makeFemale @gender = 1 end # @return [Boolean] whether this Pokémon is male def male? return self.gender == 0; end # @return [Boolean] whether this Pokémon is female def female? return self.gender == 1; end # @return [Boolean] whether this Pokémon is genderless def genderless? return self.gender == 2; end # @return [Boolean] whether this Pokémon species is restricted to only ever being one # gender (or genderless) def singleGendered? gender_ratio = species_data.gender_ratio return [:AlwaysMale, :AlwaysFemale, :Genderless].include?(gender_ratio) end #============================================================================= # Shininess #============================================================================= # @return [Boolean] whether this Pokémon is shiny (differently colored) def shiny? if @shiny.nil? a = @personalID ^ @owner.id b = a & 0xFFFF c = (a >> 16) & 0xFFFF d = b ^ c is_shiny = d < Settings::SHINY_POKEMON_CHANCE if is_shiny @shiny = true @natural_shiny = true end end if @shiny && Settings::SHINY_POKEMON_CHANCE != S_CHANCE_VALIDATOR @debug_shiny = true @natural_shiny = false end return @shiny end #============================================================================= # Ability #============================================================================= # @return [Integer] the index of this Pokémon's ability def ability_index @ability_index = (@personalID & 1) if !@ability_index return @ability_index end def forced_ability return @ability end # @return [GameData::Ability, nil] an Ability object corresponding to this Pokémon's ability def ability return GameData::Ability.try_get(ability_id) end # @return [Symbol, nil] the ability symbol of this Pokémon's ability def ability_id if !@ability sp_data = species_data abil_index = ability_index if abil_index >= 2 # Hidden ability @ability = sp_data.hidden_abilities[abil_index - 2] abil_index = (@personalID & 1) if !@ability end if !@ability # Natural ability or no hidden ability defined @ability = sp_data.abilities[abil_index] || sp_data.abilities[0] end end return @ability end def ability=(value) return if value && !GameData::Ability.exists?(value) @ability = (value) ? GameData::Ability.get(value).id : value end # Returns whether this Pokémon has a particular ability. If no value # is given, returns whether this Pokémon has an ability set. # @param check_ability [Symbol, GameData::Ability, Integer, nil] ability ID to check # @return [Boolean] whether this Pokémon has a particular ability or # an ability at all def hasAbility?(check_ability = nil) current_ability = self.ability return !current_ability.nil? if check_ability.nil? return current_ability == check_ability end # @return [Boolean] whether this Pokémon has a hidden ability def hasHiddenAbility? return ability_index >= 2 end # @return [Array>] the abilities this Pokémon can have, # where every element is [ability ID, ability index] def getAbilityList ret = [] sp_data = species_data sp_data.abilities.each_with_index { |a, i| ret.push([a, i]) if a } sp_data.hidden_abilities.each_with_index { |a, i| ret.push([a, i + 2]) if a } return ret end #============================================================================= # Nature #============================================================================= # @return [GameData::Nature, nil] a Nature object corresponding to this Pokémon's nature def nature @nature = GameData::Nature.get(@personalID % (GameData::Nature::DATA.keys.length / 2)).id if !@nature return GameData::Nature.try_get(@nature) end def nature_id return @nature end # Sets this Pokémon's nature to a particular nature. # @param value [Symbol, String, Integer, nil] nature to change to def nature=(value) return if value && !GameData::Nature.exists?(value) @nature = (value) ? GameData::Nature.get(value).id : value calc_stats if !@nature_for_stats end # Returns the calculated nature, taking into account things that change its # stat-altering effect (i.e. Gen 8 mints). Only used for calculating stats. # @return [GameData::Nature, nil] this Pokémon's calculated nature def nature_for_stats return GameData::Nature.try_get(@nature_for_stats) if @nature_for_stats return self.nature end def nature_for_stats_id return @nature_for_stats end # If defined, this Pokémon's nature is considered to be this when calculating stats. # @param value [Integer, nil] ID of the nature to use for calculating stats def nature_for_stats=(value) return if value && !GameData::Nature.exists?(value) @nature_for_stats = (value) ? GameData::Nature.get(value).id : value calc_stats end # Returns whether this Pokémon has a particular nature. If no value is given, # returns whether this Pokémon has a nature set. # @param check_nature [Integer] nature ID to check # @return [Boolean] whether this Pokémon has a particular nature or a nature # at all def hasNature?(check_nature = nil) return !@nature_id.nil? if check_nature.nil? return self.nature == check_nature end #============================================================================= # Items #============================================================================= # @return [GameData::Item, nil] an Item object corresponding to this Pokémon's item def item return GameData::Item.try_get(@item) end def item_id return @item end # Gives an item to this Pokémon to hold. # @param value [Symbol, GameData::Item, Integer, nil] ID of the item to give # to this Pokémon def item=(value) return if value && !GameData::Item.exists?(value) @item = (value) ? GameData::Item.get(value).id : value end # Returns whether this Pokémon is holding an item. If an item id is passed, # returns whether the Pokémon is holding that item. # @param check_item [Symbol, GameData::Item, Integer] item ID to check # @return [Boolean] whether the Pokémon is holding the specified item or # an item at all def hasItem?(check_item = nil) return !@item.nil? if check_item.nil? held_item = self.item return held_item && held_item == check_item end # @return [Array] the items this species can be found holding in the wild def wildHoldItems sp_data = species_data return [sp_data.wild_item_common, sp_data.wild_item_uncommon, sp_data.wild_item_rare] end # @return [Mail, nil] mail held by this Pokémon (nil if there is none) def mail @mail = nil if @mail && (!@mail.item || !hasItem?(@mail.item)) return @mail end # If mail is a Mail object, gives that mail to this Pokémon. If nil is given, # removes the held mail. # @param mail [Mail, nil] mail to be held by this Pokémon def mail=(mail) if !mail.nil? && !mail.is_a?(Mail) raise ArgumentError, _INTL('Invalid value {1} given', mail.inspect) end @mail = mail end #============================================================================= # Moves #============================================================================= # @return [Integer] the number of moves known by the Pokémon def numMoves return @moves.length end # @param move_id [Symbol, String, Integer] ID of the move to check # @return [Boolean] whether the Pokémon knows the given move def hasMove?(move_id) move_data = GameData::Move.try_get(move_id) return false if !move_data return @moves.any? { |m| m.id == move_data.id } end # Returns the list of moves this Pokémon can learn by levelling up. # @return [Array>] this Pokémon's move list, where every element is [level, move ID] def getMoveList return species_data.moves end # Sets this Pokémon's movelist to the default movelist it originally had. def reset_moves this_level = self.level # Find all level-up moves that self could have learned moveset = self.getMoveList knowable_moves = [] moveset.each { |m| knowable_moves.push(m[1]) if m[0] <= this_level } # Remove duplicates (retaining the latest copy of each move) knowable_moves = knowable_moves.reverse knowable_moves |= [] knowable_moves = knowable_moves.reverse # Add all moves @moves.clear first_move_index = knowable_moves.length - MAX_MOVES first_move_index = 0 if first_move_index < 0 for i in first_move_index...knowable_moves.length move = Pokemon::Move.new(knowable_moves[i]) @moves.push(move) @learned_moves = [] if !@learned_moves @learned_moves << move if !@learned_moves.include?(move) end end def add_learned_move(move) @learned_moves = [] if !@learned_moves @learned_moves << move unless @learned_moves.include?(move) end # Silently learns the given move. Will erase the first known move if it has to. # @param move_id [Symbol, String, Integer] ID of the move to learn def learn_move(move_id) move_data = GameData::Move.try_get(move_id) return if !move_data # Check if self already knows the move; if so, move it to the end of the array @moves.each_with_index do |m, i| next if m.id != move_data.id @moves.push(m) @moves.delete_at(i) return end move = Pokemon::Move.new(move_data.id) # Move is not already known; learn it @moves.push(move) # Delete the first known move if self now knows more moves than it should @moves.shift if numMoves > MAX_MOVES @learned_moves = [] if !@learned_moves @learned_moves << move if !@learned_moves.include?(move) echoln @learned_moves end # Deletes the given move from the Pokémon. # @param move_id [Symbol, String, Integer] ID of the move to delete def forget_move(move_id) move_data = GameData::Move.try_get(move_id) return if !move_data @moves.delete_if { |m| m.id == move_data.id } end # Deletes the move at the given index from the Pokémon. # @param index [Integer] index of the move to be deleted def forget_move_at_index(index) @moves.delete_at(index) end # Deletes all moves from the Pokémon. def forget_all_moves @moves.clear end # Copies currently known moves into a separate array, for Move Relearner. def record_first_moves clear_first_moves @moves.each { |m| @first_moves.push(m.id) } end # Adds a move to this Pokémon's first moves. # @param move_id [Symbol, String, Integer] ID of the move to add def add_first_move(move_id) move_data = GameData::Move.try_get(move_id) @first_moves.push(move_data.id) if move_data && !@first_moves.include?(move_data.id) end # Removes a move from this Pokémon's first moves. # @param move_id [Symbol, String, Integer] ID of the move to remove def remove_first_move(move_id) move_data = GameData::Move.try_get(move_id) @first_moves.delete(move_data.id) if move_data end # Clears this Pokémon's first moves. def clear_first_moves @first_moves.clear end # @param move_id [Symbol, String, Integer] ID of the move to check # @return [Boolean] whether the Pokémon is compatible with the given move def compatible_with_move?(move_id) move_data = GameData::Move.try_get(move_id) if isFusion?() head_species_id = getBasePokemonID(species, false) body_species_id = getBasePokemonID(species) head_species = GameData::Species.get(head_species_id) body_species = GameData::Species.get(body_species_id) return move_data && (pokemon_can_learn_move(head_species, move_data) || pokemon_can_learn_move(body_species, move_data)) else return move_data && pokemon_can_learn_move(species_data, move_data) end end def pokemon_can_learn_move(species_data, move_data) return species_data.tutor_moves.include?(move_data.id) || species_data.moves.include?(move_data.id) || species_data.egg_moves.include?(move_data.id) end def can_relearn_move? return false if egg? || shadowPokemon? this_level = self.level getMoveList.each { |m| return true if m[0] <= this_level && !hasMove?(m[1]) } @first_moves.each { |m| return true if !hasMove?(m) } return false end def has_egg_move? return false if egg? || shadowPokemon? baby = pbGetBabySpecies(self.species) moves = pbGetSpeciesEggMoves(baby) return true if moves.size >= 1 end #============================================================================= # Ribbons #============================================================================= # @return [Integer] the number of ribbons this Pokémon has def numRibbons return @ribbons.length end # @param ribbon [Symbol, String, GameData::Ribbon, Integer] ribbon ID to check for # @return [Boolean] whether this Pokémon has the specified ribbon def hasRibbon?(ribbon) ribbon_data = GameData::Ribbon.try_get(ribbon) return ribbon_data && @ribbons.include?(ribbon_data.id) end # Gives a ribbon to this Pokémon. # @param ribbon [Symbol, String, GameData::Ribbon, Integer] ID of the ribbon to give def giveRibbon(ribbon) ribbon_data = GameData::Ribbon.try_get(ribbon) return if !ribbon_data || @ribbons.include?(ribbon_data.id) @ribbons.push(ribbon_data.id) end # Replaces one ribbon with the next one along, if possible. If none of the # given ribbons are owned, give the first one. # @return [Symbol, nil] ID of the ribbon that was gained def upgradeRibbon(*arg) for i in 0...arg.length - 1 this_ribbon_data = GameData::Ribbon.try_get(i) next if !this_ribbon_data for j in 0...@ribbons.length next if @ribbons[j] != this_ribbon_data.id next_ribbon_data = GameData::Ribbon.try_get(arg[i + 1]) next if !next_ribbon_data @ribbons[j] = next_ribbon_data.id return @ribbons[j] end end first_ribbon_data = GameData::Ribbon.try_get(arg[0]) last_ribbon_data = GameData::Ribbon.try_get(arg[arg.length - 1]) if first_ribbon_data && last_ribbon_data && !hasRibbon?(last_ribbon_data.id) giveRibbon(first_ribbon_data.id) return first_ribbon_data.id end return nil end # Removes the specified ribbon from this Pokémon. # @param ribbon [Symbol, String, GameData::Ribbon, Integer] ID of the ribbon to remove def takeRibbon(ribbon) ribbon_data = GameData::Ribbon.try_get(ribbon) return if !ribbon_data for i in 0...@ribbons.length next if @ribbons[i] != ribbon_data.id @ribbons[i] = nil @ribbons.compact! break end end # Removes all ribbons from this Pokémon. def clearAllRibbons @ribbons.clear end #============================================================================= # Pokérus #============================================================================= # @return [Integer] the Pokérus infection stage for this Pokémon def pokerusStrain return @pokerus / 16 end # Returns the Pokérus infection stage for this Pokémon. The possible stages are # 0 (not infected), 1 (infected) and 2 (cured) # @return [0, 1, 2] current Pokérus infection stage def pokerusStage return 0 if @pokerus == 0 return ((@pokerus % 16) == 0) ? 2 : 1 end # Gives this Pokémon Pokérus (either the specified strain or a random one). # @param strain [Integer] Pokérus strain to give def givePokerus(strain = 0) return if self.pokerusStage == 2 # Can't re-infect a cured Pokémon strain = rand(1..16) if strain <= 0 || strain >= 16 time = 1 + (strain % 4) @pokerus = time @pokerus |= strain << 4 end # Resets the infection time for this Pokémon's Pokérus (even if cured). def resetPokerusTime return if @pokerus == 0 strain = @pokerus % 16 time = 1 + (strain % 4) @pokerus = time @pokerus |= strain << 4 end # Reduces the time remaining for this Pokémon's Pokérus (if infected). def lowerPokerusCount return if self.pokerusStage != 1 @pokerus -= 1 end #============================================================================= # Ownership, obtained information #============================================================================= # Changes this Pokémon's owner. # @param new_owner [Owner] the owner to change to def owner=(new_owner) validate new_owner => Owner @owner = new_owner end # @param trainer [Player, NPCTrainer] the trainer to compare to the original trainer # @return [Boolean] whether the given trainer is not this Pokémon's original trainer def foreign?(trainer) return @owner.id != trainer.id# || @owner.name != trainer.name end def always_disobey(value) @force_disobey = value end # @return [Time] the time when this Pokémon was obtained def timeReceived return Time.at(@timeReceived) end # Sets the time when this Pokémon was obtained. # @param value [Integer, Time, #to_i] time in seconds since Unix epoch def timeReceived=(value) @timeReceived = value.to_i end # @return [Time] the time when this Pokémon hatched def timeEggHatched return (obtain_method == 1) ? Time.at(@timeEggHatched) : nil end # Sets the time when this Pokémon hatched. # @param value [Integer, Time, #to_i] time in seconds since Unix epoch def timeEggHatched=(value) @timeEggHatched = value.to_i end #============================================================================= # Other #============================================================================= # @return [String] the name of this Pokémon def name return (nicknamed?) ? @name : speciesName end # @param value [String] the nickname of this Pokémon def name=(value) value = nil if !value || value.empty? || value == speciesName @name = value end # @return [Boolean] whether this Pokémon has been nicknamed def nicknamed? return @name && !@name.empty? end # @return [String] the species name of this Pokémon def speciesName return species_data.name end # @return [Integer] the height of this Pokémon in metres def height return species_data.height/10 end # @return [Integer] the weight of this Pokémon in kilograms def weight return species_data.weight/10 end # @return [Hash] the EV yield of this Pokémon (a hash with six key/value pairs) def evYield this_evs = species_data.evs ret = {} GameData::Stat.each_main { |s| ret[s.id] = this_evs[s.id] } return ret end # Changes the happiness of this Pokémon depending on what happened to change it. # @param method [String] the happiness changing method (e.g. 'walking') def changeHappiness(method) gain = 0 happiness_range = @happiness / 100 case method when "walking" gain = [2, 2, 1][happiness_range] when "levelup" gain = [5, 4, 3][happiness_range] when "groom" gain = [10, 10, 4][happiness_range] when "evberry" gain = [10, 5, 2][happiness_range] when "vitamin" gain = [5, 3, 2][happiness_range] when "wing" gain = [3, 2, 1][happiness_range] when "machine", "battleitem" gain = [1, 1, 0][happiness_range] when "faint" gain = -1 when "faintbad" # Fainted against an opponent that is 30+ levels higher gain = [-5, -5, -10][happiness_range] when "powder" gain = [-5, -5, -10][happiness_range] when "energyroot" gain = [-10, -10, -15][happiness_range] when "revivalherb" gain = [-15, -15, -20][happiness_range] else raise _INTL("Unknown happiness-changing method: {1}", method.to_s) end if gain > 0 gain += 1 if @obtain_map == $game_map.map_id gain += 1 if @poke_ball == :LUXURYBALL gain = (gain * 1.5).floor if hasItem?(:SOOTHEBELL) end @happiness = (@happiness + gain).clamp(0, 255) end #============================================================================= # Evolution checks #============================================================================= # Checks whether this Pokemon can evolve because of levelling up. # @return [Symbol, nil] the ID of the species to evolve into def prompt_evolution_choice(body_evolution, head_evolution) current_body = @species_data.body_pokemon current_head = @species_data.head_pokemon choices = [ #_INTL("Evolve both!"), _INTL("Evolve head!"), _INTL("Evolve body!"), _INTL("Don't evolve") ] choice = pbMessage(_INTL('Both halves of {1} are ready to evolve!', self.name), choices, 0) # if choice == 0 #EVOLVE BOTH # newspecies = getFusionSpecies(body_evolution,head_evolution) if choice == 0 #EVOLVE HEAD newspecies = getFusionSpecies(current_body, head_evolution) elsif choice == 1 #EVOLVE BODY newspecies = getFusionSpecies(body_evolution, current_head) else newspecies = nil end return newspecies end def check_evolution_on_level_up if @species_data.is_a?(GameData::FusedSpecies) body = self.species_data.body_pokemon head = self.species_data.head_pokemon body_evolution = check_evolution_internal(@species_data.body_pokemon) { |pkmn, new_species, method, parameter| success = GameData::Evolution.get(method).call_level_up(pkmn, parameter) next (success) ? new_species : nil } head_evolution = check_evolution_internal(@species_data.head_pokemon) { |pkmn, new_species, method, parameter| success = GameData::Evolution.get(method).call_level_up(pkmn, parameter) next (success) ? new_species : nil } if body_evolution && head_evolution return prompt_evolution_choice(body_evolution, head_evolution) end end return check_evolution_internal { |pkmn, new_species, method, parameter| success = GameData::Evolution.get(method).call_level_up(pkmn, parameter) next (success) ? new_species : nil } end # Checks whether this Pokemon can evolve because of using an item on it. # @param item_used [Symbol, GameData::Item, nil] the item being used # @return [Symbol, nil] the ID of the species to evolve into def check_evolution_on_use_item(item_used) return check_evolution_internal { |pkmn, new_species, method, parameter| success = GameData::Evolution.get(method).call_use_item(pkmn, parameter, item_used) next (success) ? new_species : nil } end # Checks whether this Pokemon can evolve because of being traded. # @param other_pkmn [Pokemon] the other Pokémon involved in the trade # @return [Symbol, nil] the ID of the species to evolve into def check_evolution_on_trade(other_pkmn) return check_evolution_internal { |pkmn, new_species, method, parameter| success = GameData::Evolution.get(method).call_on_trade(pkmn, parameter, other_pkmn) next (success) ? new_species : nil } end # Called after this Pokémon evolves, to remove its held item (if the evolution # required it to have a held item) or duplicate this Pokémon (Shedinja only). # @param new_species [Pokemon] the species that this Pokémon evolved into def action_after_evolution(new_species) species_data.get_evolutions(true).each do |evo| # [new_species, method, parameter] break if GameData::Evolution.get(evo[1]).call_after_evolution(self, evo[0], evo[2], new_species) end end # The core method that performs evolution checks. Needs a block given to it, # which will provide either a GameData::Species ID (the species to evolve # into) or nil (keep checking). # @return [Symbol, nil] the ID of the species to evolve into def check_evolution_internal(species = nil) return nil if egg? || shadowPokemon? return nil if hasItem?(:EVERSTONE) return nil if hasAbility?(:BATTLEBOND) species = species_data if !species species.get_evolutions(true).each do |evo| # [new_species, method, parameter, boolean] next if evo[3] # Prevolution ret = yield self, evo[0], evo[1], evo[2] # pkmn, new_species, method, parameter return ret if ret end return nil end #============================================================================= # Stat calculations #============================================================================= def getBaseStatsFormException() if @species == :PUMPKABOO case @size_category when :SMALL return { :HP => 44, :ATTACK => 66, :DEFENSE => 70, :SPECIAL_ATTACK => 44, :SPECIAL_DEFENSE => 55, :SPEED => 56} when :AVERAGE return nil when :LARGE return { :HP => 54, :ATTACK => 66, :DEFENSE => 70, :SPECIAL_ATTACK => 44, :SPECIAL_DEFENSE => 55, :SPEED => 46} when :SUPER return { :HP => 59, :ATTACK => 66, :DEFENSE => 70, :SPECIAL_ATTACK => 44, :SPECIAL_DEFENSE => 55, :SPEED => 41} end end if @species == :GOURGEIST case @size_category when :SMALL return { :HP => 55, :ATTACK => 85, :DEFENSE => 122, :SPECIAL_ATTACK => 58, :SPECIAL_DEFENSE => 75, :SPEED => 99} when :AVERAGE return nil when :LARGE return { :HP => 75, :ATTACK => 95, :DEFENSE => 122, :SPECIAL_ATTACK => 58, :SPECIAL_DEFENSE => 75, :SPEED => 69} when :SUPER return { :HP => 85, :ATTACK => 100, :DEFENSE => 122, :SPECIAL_ATTACK => 58, :SPECIAL_DEFENSE => 75, :SPEED => 54} end end return nil end # @return [Hash] this Pokémon's base stats, a hash with six key/value pairs def baseStats this_base_stats = species_data.base_stats base_stats_exception = getBaseStatsFormException() this_base_stats = base_stats_exception if base_stats_exception ret = {} GameData::Stat.each_main { |s| ret[s.id] = this_base_stats[s.id] } return ret end # Returns this Pokémon's effective IVs, taking into account Hyper Training. # Only used for calculating stats. # @return [Hash] hash containing this Pokémon's effective IVs def calcIV this_ivs = self.iv ret = {} GameData::Stat.each_main do |s| ret[s.id] = (@ivMaxed[s.id]) ? IV_STAT_LIMIT : this_ivs[s.id] end return ret end # @return [Integer] the maximum HP of this Pokémon def calcHP(base, level, iv, ev) return 1 if base == 1 # For Shedinja return ((base * 2 + iv + (ev / 4)) * level / 100).floor + level + 10 end # @return [Integer] the specified stat of this Pokémon (not used for total HP) def calcStat(base, level, iv, ev, nat) return ((((base * 2 + iv + (ev / 4)) * level / 100).floor + 5) * nat / 100).floor end def adjust_level_for_base_stats_mode() nb_badges = $Trainer.badge_count this_level = ((nb_badges * Settings::NO_LEVEL_MODE_LEVEL_INCR) + Settings::NO_LEVEL_MODE_LEVEL_BASE).ceil if this_level > Settings::MAXIMUM_LEVEL this_level = Settings::MAXIMUM_LEVEL end return this_level end def adjustHPForWonderGuard(stats) return self.ability == :WONDERGUARD ? 1 : stats[:HP] end def checkHPRelatedFormChange() if @ability == :SHIELDSDOWN return if $game_temp.in_battle #handled in battlers class in-battle if isFusionOf(:MINIOR_M) if @hp <= (@totalhp / 2) changeFormSpecies(:MINIOR_M, :MINIOR_C) end end if isFusionOf(:MINIOR_C) if @hp > (@totalhp / 2) changeFormSpecies(:MINIOR_C, :MINIOR_M) end end end end # Recalculates this Pokémon's stats. def calc_stats base_stats = self.baseStats this_level = self.level this_IV = self.calcIV if $game_switches[SWITCH_NO_LEVELS_MODE] this_level = adjust_level_for_base_stats_mode() end # Format stat multipliers due to nature nature_mod = {} GameData::Stat.each_main { |s| nature_mod[s.id] = 100 } this_nature = self.nature_for_stats if this_nature this_nature.stat_changes.each { |change| nature_mod[change[0]] += change[1] } end # Calculate stats stats = {} GameData::Stat.each_main do |s| if s.id == :HP stats[s.id] = calcHP(base_stats[s.id], this_level, this_IV[s.id], @ev[s.id]) else stats[s.id] = calcStat(base_stats[s.id], this_level, this_IV[s.id], @ev[s.id], nature_mod[s.id]) end end hpDiff = @totalhp - @hp #@totalhp = stats[:HP] @totalhp = adjustHPForWonderGuard(stats) calculated_hp = @totalhp - hpDiff @hp = calculated_hp > 0 ? calculated_hp : 0 @attack = stats[:ATTACK] @defense = stats[:DEFENSE] @spatk = stats[:SPECIAL_ATTACK] @spdef = stats[:SPECIAL_DEFENSE] @speed = stats[:SPEED] end #============================================================================= # Pokémon creation #============================================================================= # Creates a copy of this Pokémon and returns it. # @return [Pokemon] a copy of this Pokémon def clone ret = super ret.iv = {} ret.ivMaxed = {} ret.ev = {} GameData::Stat.each_main do |s| ret.iv[s.id] = @iv[s.id] ret.ivMaxed[s.id] = @ivMaxed[s.id] ret.ev[s.id] = @ev[s.id] end ret.moves = [] @moves.each_with_index { |m, i| ret.moves[i] = m.clone } ret.first_moves = @first_moves.clone ret.owner = @owner.clone ret.ribbons = @ribbons.clone return ret end # Creates a new Pokémon object. # @param species [Symbol, String, Integer] Pokémon species # @param level [Integer] Pokémon level # @param owner [Owner, Player, NPCTrainer] Pokémon owner (the player by default) # @param withMoves [TrueClass, FalseClass] whether the Pokémon should have moves # @param rechech_form [TrueClass, FalseClass] whether to auto-check the form def determine_scale return :AVERAGE if !@size_category size_roll = rand(100) # Random number between 0-99 if @size_category == :SMALL return 0.75 elsif @size_category == :AVERAGE return 1 elsif @size_category == :LARGE return 1 + (1.0 /3) #"Large Size" elsif @size_category == :SUPER return 1 + (2.0 /3) #"Super Size" end return 1 end def determine_size_category return :AVERAGE if !(Kernel.isPartPokemon(self,:PUMPKABOO) || Kernel.isPartPokemon(self,:GOURGEIST)) size_roll = rand(100) # Random number between 0-99 if size_roll < 10 return :SMALL elsif size_roll < 50 return :AVERAGE elsif size_roll < 90 return :LARGE else return :SUPER end end def initialize(species, level, owner = $Trainer, withMoves = true, recheck_form = true) @species_data = GameData::Species.get(species) @species = @species_data.species @form = @species_data.form @forced_form = nil @time_form_set = nil self.level = level @steps_to_hatch = 0 heal_status @gender = nil @shiny = nil @ability_index = nil @ability2_index = nil @ability = nil @ability2 = nil @nature = nil @nature_for_stats = nil @item = nil @mail = nil @moves = [] @learned_moves = [] reset_moves if withMoves @first_moves = [] @ribbons = [] @cool = 0 @beauty = 0 @cute = 0 @smart = 0 @tough = 0 @sheen = 0 @pokerus = 0 @name = nil @happiness = @species_data.happiness @poke_ball = :POKEBALL @markings = 0 @iv = {} @ivMaxed = {} @ev = {} @hiddenPowerType = nil @glitter = nil GameData::Stat.each_main do |s| @iv[s.id] = rand(IV_STAT_LIMIT + 1) @ev[s.id] = 0 end if owner.is_a?(Owner) @owner = owner elsif owner.is_a?(Player) || owner.is_a?(NPCTrainer) @owner = Owner.new_from_trainer(owner) else @owner = Owner.new(0, '', 2, 2) end @obtain_method = 0 # Met @obtain_method = 4 if $game_switches && $game_switches[Settings::FATEFUL_ENCOUNTER_SWITCH] @obtain_map = ($game_map) ? $game_map.map_id : 0 @obtain_text = nil @obtain_level = level @hatched_map = 0 @timeReceived = Time.new.to_i @timeEggHatched = nil @fused = nil @personalID = rand(2 ** 16) | rand(2 ** 16) << 16 @hp = 1 @totalhp = 1 @spriteform_body = nil @spriteform_head = nil @hat = nil @hat_x = 0 @hat_y = 0 @size_category = determine_size_category() @sprite_scale=determine_scale() echoln @sprite_scale calc_stats if @form == 0 && recheck_form f = MultipleForms.call("getFormOnCreation", self) if f self.form = f reset_moves if withMoves end end end def totalIv() end end