TRAINER_CLASS_FAVORITE_TYPES = { AROMALADY: [:GRASS, :FAIRY], BEAUTY: [:FAIRY, :WATER, :NORMAL, :GRASS], BIKER: [:POISON, :DARK], BIRDKEEPER: [:FLYING, :NORMAL], BUGCATCHER: [:BUG], BURGLAR: [:FIRE, :DARK], CHANNELER: [:GHOST, :PSYCHIC], CUEBALL: [:FIGHTING, :STEEL], ENGINEER: [:ELECTRIC, :STEEL], FISHERMAN: [:WATER], GAMBLER: [:NORMAL, :PSYCHIC], GENTLEMAN: [:NORMAL, :STEEL], HIKER: [:ROCK, :GROUND], JUGGLER: [:PSYCHIC, :GHOST, :NORMAL, :POISON], LADY: [:FAIRY, :NORMAL], PAINTER: [:NORMAL, :PSYCHIC, :GRASS], POKEMANIAC: [:DRAGON, :GROUND], POKEMONBREEDER: [:NORMAL, :GRASS], PROFESSOR: [:NORMAL, :PSYCHIC], ROCKER: [:ELECTRIC, :FIRE], RUINMANIAC: [:GROUND, :ROCK, :PSYCHIC], SAILOR: [:WATER, :FIGHTING], SCIENTIST: [:ELECTRIC, :STEEL, :POISON], SUPERNERD: [:ELECTRIC, :PSYCHIC, :STEEL], TAMER: [:NORMAL, :DARK], BLACKBELT: [:FIGHTING], CRUSHGIRL: [:FIGHTING], CAMPER: [:BUG, :NORMAL, :GRASS], PICNICKER: [:GRASS, :NORMAL], COOLTRAINER_M: [:DRAGON, :STEEL, :FIRE], COOLTRAINER_F: [:ICE, :PSYCHIC, :FAIRY], YOUNGSTER: [:NORMAL, :BUG, :GRASS, :FLYING], LASS: [:NORMAL, :FAIRY], POKEMONRANGER_M: [:GRASS, :GROUND], POKEMONRANGER_F: [:GRASS, :WATER], PSYCHIC_M: [:PSYCHIC, :GHOST], PSYCHIC_F: [:PSYCHIC, :FAIRY], SWIMMER_M: [:WATER, :ICE], SWIMMER_F: [:WATER, :ICE], SWIMMER2_M: [:WATER], SWIMMER2_F: [:WATER], TUBER_M: [:WATER], TUBER_F: [:WATER], TUBER2_M: [:WATER], TUBER2_F: [:WATER], COOLCOUPLE: [:FIRE, :ICE], CRUSHKIN: [:FIGHTING], SISANDBRO: [:WATER, :GROUND], TWINS: [:FAIRY, :NORMAL], YOUNGCOUPLE: [:NORMAL, :PSYCHIC], SOCIALITE: [:FAIRY, :NORMAL], BUGCATCHER_F: [:BUG], ROUGHNECK: [:DARK, :FIGHTING], TEACHER: [:PSYCHIC, :NORMAL], PRESCHOOLER_M: [:NORMAL, :BUG], PRESCHOOLER_F: [:FAIRY, :NORMAL], HAUNTEDGIRL_YOUNG: [:GHOST], HAUNTEDGIRL: [:GHOST, :DARK], CLOWN: [:PSYCHIC, :FAIRY], NURSE: [:NORMAL, :FAIRY], WORKER: [:STEEL, :GROUND], COOLTRAINER_M2: [:FIGHTING, :STEEL], COOLTRAINER_F2: [:PSYCHIC, :ICE], FARMER: [:GRASS, :GROUND, :NORMAL], PYROMANIAC: [:FIRE], KIMONOGIRL: [:FAIRY, :PSYCHIC, :GHOST], SAGE: [:PSYCHIC, :GHOST], PLAYER: [:ICE, :FIGHTING], POLICE: [:DARK, :FIGHTING], SKIER_F: [:ICE], DELIVERYMAN: [:NORMAL], } #Higher values: pickier TRAINER_CLASS_PICKINESS = { AROMALADY: 1.8, BEAUTY: 2.2, BIKER: 1.2, BIRDKEEPER: 1.4, BUGCATCHER: 1.1, BURGLAR: 1.3, CHANNELER: 1.7, CUEBALL: 1.3, ENGINEER: 2.0, FISHERMAN: 1.4, GAMBLER: 1.5, GENTLEMAN: 2.3, HIKER: 1.5, JUGGLER: 1.8, LADY: 2.4, PAINTER: 2.0, POKEMANIAC: 1.6, POKEMONBREEDER: 1.9, PROFESSOR: 2.5, ROCKER: 1.4, RUINMANIAC: 1.5, SAILOR: 1.3, SCIENTIST: 2.0, SUPERNERD: 1.9, TAMER: 1.5, BLACKBELT: 1.7, CRUSHGIRL: 1.6, CAMPER: 1.2, PICNICKER: 1.2, COOLTRAINER_M: 2.4, COOLTRAINER_F: 2.4, YOUNGSTER: 0.9, LASS: 1.0, POKEMONRANGER_M: 2.0, POKEMONRANGER_F: 2.0, PSYCHIC_M: 2.0, PSYCHIC_F: 2.0, SWIMMER_M: 1.0, SWIMMER_F: 1.0, SWIMMER2_M: 1.0, SWIMMER2_F: 1.0, TUBER_M: 0.8, TUBER_F: 0.8, TUBER2_M: 0.8, TUBER2_F: 0.8, COOLCOUPLE: 2.1, CRUSHKIN: 1.7, SISANDBRO: 1.3, TWINS: 1.0, YOUNGCOUPLE: 1.6, SOCIALITE: 2.3, BUGCATCHER_F: 1.1, ROUGHNECK: 1.4, TEACHER: 2.0, PRESCHOOLER_M: 0.6, PRESCHOOLER_F: 0.6, HAUNTEDGIRL_YOUNG: 1.3, HAUNTEDGIRL: 1.7, CLOWN: 1.5, NURSE: 2.0, WORKER: 1.6, COOLTRAINER_M2: 2.4, COOLTRAINER_F2: 2.4, FARMER: 1.5, PYROMANIAC: 1.6, KIMONOGIRL: 2.2, SAGE: 2.1, PLAYER: 1.0, POLICE: 1.8, SKIER_F: 1.6, DELIVERYMAN: 1.3 } def evaluate_pokemon_worth(pkmn, compare_level: nil) species_data = pkmn.species_data return 0 unless species_data level = pkmn.level level_diff = compare_level ? (level - compare_level) : 0 level_score = level * 2 + [level_diff, 0].max * 1.5 # bonus if player's level is higher base_stats_score = species_data.base_stats.values.sum / 10.0 rarity_score = (255 - species_data.catch_rate) / 5.0 iv_score = (pkmn.iv&.values&.sum || 0) / 4.0 shiny_score = pkmn.shiny? ? 50 : 0 fusion_bonus = pkmn.isFusion? ? 40 : 0 score = level_score + base_stats_score + rarity_score + iv_score + shiny_score + fusion_bonus echoln("#{pkmn.name} - Score : #{score}") return score end def offerPokemonForTrade(player_pokemon, npc_party, trainer_class) player_score = evaluate_pokemon_worth(player_pokemon) pickiness = TRAINER_CLASS_PICKINESS[trainer_class] || 1.0 # Evaluate all NPC Pokémon scores npc_scores = npc_party.map do |npc_pkmn| [npc_pkmn, evaluate_pokemon_worth(npc_pkmn, compare_level: player_pokemon.level)] end best_npc_pokemon, best_score = npc_scores.max_by { |_, score| score } return best_npc_pokemon if player_score > best_score max_difference = [player_score, 100].min * pickiness candidates = npc_scores.select do |npc_pkmn, npc_score| (npc_score - player_score).abs <= max_difference end return nil if candidates.empty? candidates.min_by do |_, npc_score| (npc_score - player_score).abs end.first end def doNPCTrainerTrade(trainer) echoln trainer.getTimeSinceLastTrade if trainer.isNextTradeReady? pbMessage(_INTL("The trainer is not ready to trade yet. Wait a little bit before you make your offer.")) return trainer end return generateTrainerTradeOffer(trainer) end #prefered type depends on the trainer class # def generateTrainerTradeOffer(trainer) bg_image_id=20 wanted_type = trainer.favorite_type wanted_type = :NORMAL if !wanted_type wanted_type_name = GameData::Type.get(wanted_type).real_name trainerClassName = GameData::TrainerType.get(trainer.trainerType).real_name pbMessage(_INTL("#{trainerClassName} #{trainer.trainerName} is looking for \\C[1]#{wanted_type_name}-type Pokémon\\C[0]. Which Pokémon do you want to trade?."),) pbChoosePokemon(1,2, proc {|pokemon| pokemon.hasType?(wanted_type) }) chosen_index = pbGet(1) echoln pbGet(1) if chosen_index && chosen_index >= 0 chosen_pokemon = $Trainer.party[chosen_index] offered_pokemon = offerPokemonForTrade(chosen_pokemon,trainer.currentTeam,trainer.trainerType) if !offered_pokemon pbMessage(_INTL("#{trainerClassName} #{trainer.trainerName} does not want to trade...")) return trainer end pif_sprite = BattleSpriteLoader.new.get_pif_sprite_from_species(offered_pokemon.species) pif_sprite.dump_info() message = _INTL("#{trainerClassName} #{trainer.trainerName} is offering #{offered_pokemon.name} (Level #{offered_pokemon.level}) for your #{chosen_pokemon.name}.") showPokemonInPokeballWithMessage(pif_sprite, message) if pbConfirmMessage(_INTL("Trade away #{chosen_pokemon.name} for #{trainerClassName} #{trainer.trainerName}'s #{offered_pokemon.name}?")) pbStartTrade(chosen_index, offered_pokemon,offered_pokemon.name,trainer.trainerName,0) updated_party = trainer.currentTeam updated_party.delete(offered_pokemon) updated_party << chosen_pokemon.clone trainer.previous_trade_timestamp= Time.now trainer.increase_friendship(20) return trainer end end return trainer #todo # # NPC says "I'm looking for X or Y tyflpe Pokemon (prefered Pokemon can be determined when initializing from a pool of types that depends on the trainer class) # Also possible to pass a list of specific Pokemon in trainers.txt that the trainer will ask for instead if it's defined # # you select one of your Pokemon and he gives you one for it # prioritize recently caught pokemon # prioritive weaker Pokemon # #Assign a score to each Pokemon in trainer's team. calculate the same score for trainer's pokemon - select which # one is closer # # NPC says "I can offer A in exchange for your B. # -Yes -> Trade, update trainer team to put the player's pokemon in there # Cannot trade again with the same trainer for 5 minutes # "You just traded with this trainer. Wait a bit before you make another offer # -No trainer.set_pending_action(false) if trainer return trainer end