Update 6.8

This commit is contained in:
chardub
2026-07-10 15:42:06 -04:00
parent 5b85e72cb2
commit 6a6f126a18
7871 changed files with 493194 additions and 224826 deletions
@@ -0,0 +1,276 @@
class PokeNavAppScene
#--------------------------------------------------------------------------
# Configuration (override in child classes)
#--------------------------------------------------------------------------
HEADER_HEIGHT = -6
def display_mode
return :LIST # :LIST or :GRID
end
def start_x
return 60;
end
def start_y
return 80;
end
def x_gap
return 220;
end
def y_gap
return 100;
end
def visible_rows
return 4;
end
def cursor_x_offset
return 0
end
def cursor_y_offset
return 0
end
def header_path
return "Graphics/Pictures/Pokegear/bg_header"
end
def bg_path
return ""
end
def cursor_path
return "Graphics/Pictures/Pokegear/icon_button"
end
def header_name
return _INTL("PokeNav App")
end
def columns
return (display_mode == :GRID) ? 2 : 1
end
def initialize
@text_color_base = pbColor(:DARK_TEXT_MAIN_COLOR)
@text_color_shadow = pbColor(:DARK_TEXT_SHADOW_COLOR)
if isDarkMode
@text_color_base, @text_color_shadow = @text_color_shadow, @text_color_base
end
end
# buttons should be a list of PokeNavButton
def pbStartScene(buttons = [])
@buttons = buttons
@index = 0
@mode = display_mode
@exiting = false
@viewport = Viewport.new(0, 0, Graphics.width, Graphics.height)
@viewport.z = 99999
@sprites = {}
createBackground
createHeader if use_header?
@buttons.each_with_index do |b, i|
b.viewport = @viewport
b.z=1
@sprites["button#{i}"] = b
end
if @buttons && !@buttons.empty?
createCursor
layoutButtons
end
init_graphics
pbUpdateSpriteHash(@sprites)
pbFadeInAndShow(@sprites) { pbUpdate }
end
def init_graphics
@sprites["bg"] = IconSprite.new(0, 0, @viewport)
@sprites["bg"].setBitmap(bg_path)
@sprites["bg"].z=0
end
def pbUpdate
layoutButtons
pbUpdateSpriteHash(@sprites)
end
def layoutButtons
return if @exiting
cols = columns
rows_visible = visible_rows
# Current selected row
current_row = @index / cols
# Scroll offset in rows
scroll_row = 0
if current_row >= rows_visible
scroll_row = current_row - rows_visible + 1
end
scroll_pixels = scroll_row * y_gap
@buttons.each_with_index do |btn, i|
row = i / cols
col = i % cols
btn.x = start_x + col * x_gap
btn.y = start_y + row * y_gap - scroll_pixels
# Only show buttons fully within the scrollable content area, never over the header
btn.visible = (btn.y >= start_y && btn.y <= Graphics.height)
btn.selected = (i == @index)
end
updateCursor
updateHeader(scroll_row)
end
def updateCursor
cursor = @sprites["cursor"]
return unless cursor
return if @buttons.empty?
btn = @buttons[@index]
return unless btn
cursor.x = btn.x + cursor_x_offset
cursor.y = btn.y + cursor_y_offset
cursor.visible = btn.visible
end
def set_cursor_visible(value)
if @sprites["cursor"]
@sprites["cursor"].visible = value
end
end
def createCursor
return unless cursor_path
@sprites["cursor"] = IconSprite.new(0, 0, @viewport)
@sprites["cursor"].setBitmap(cursor_path)
@sprites["cursor"].z = 100000
end
def createHeader
@sprites["header"] = IconSprite.new(0, 0, @viewport)
@sprites["header"].setBitmap(header_path)
# Ensure header always renders above buttons and cursor
@sprites["header"].z = 100001
end
def createBackground
@sprites["background"] = IconSprite.new(0, 0, @viewport)
if isDarkMode
@sprites["background"].setBitmap("Graphics/Pictures/Pokegear/bg_dark")
else
@sprites["background"].setBitmap("Graphics/Pictures/Pokegear/bg")
end
end
def updateHeader(scroll_row)
return unless use_header?
return unless @sprites["header"]
end
def displayTextElements
Kernel.pbClearText
showHeaderName
end
def use_header?
return true
end
def showHeaderName
Kernel.pbDisplayText(header_name, Graphics.width/2 , HEADER_HEIGHT)
end
def pbScene
loop do
Graphics.update
Input.update
updateInput
layoutButtons
pbUpdateSpriteHash(@sprites)
break if @exiting
end
end
def updateInput
cols = columns
total = @buttons.length
move_delay = 2
if Input.trigger?(Input::BACK)
pbPlayCloseMenuSE
@exiting = true
return
elsif Input.trigger?(Input::USE)
pbPlayDecisionSE
click(@buttons[@index]&.id)
elsif Input.repeat?(Input::LEFT) && cols > 1
move_index(-1)
pbWait(move_delay)
elsif Input.repeat?(Input::RIGHT) && cols > 1
move_index(1)
pbWait(move_delay)
elsif Input.repeat?(Input::UP)
move_index(-cols)
pbWait(move_delay)
elsif Input.repeat?(Input::DOWN)
move_index(cols)
pbWait(move_delay)
end
end
def hover(button_id)
@buttons[@index]&.hover
end
def click(button_id)
@buttons[@index]&.click
end
def move_index(delta)
echoln "old: #{@index}"
return if @buttons.empty?
pbPlayCursorSE
@index = (@index + delta) % @buttons.length
hover(@buttons[@index]&.id)
end
def move_to_index(index)
return if @buttons.empty?
pbPlayCursorSE
@index = index
echoln "new: #{@index}"
hover(@buttons[@index]&.id)
end
def pbEndScene
@exiting = true
pbFadeOutAndHide(@sprites) { pbUpdate }
pbDisposeSpriteHash(@sprites)
@buttons.each do |button|
button.dispose
end
Kernel.pbClearText
@viewport.dispose
end
end
@@ -0,0 +1,209 @@
#===============================================================================
#
#===============================================================================
class PokenavButton < SpriteWrapper
DESC_Y = 10
LINE_HEIGHT = 22
REWARD_LINE = DESC_Y + LINE_HEIGHT * 2
attr_reader :id
attr_accessor :height
attr_accessor :width
attr_accessor :image_path
attr_accessor :icon_bitmap
attr_accessor :x
attr_accessor :y
attr_accessor :crop_width
attr_accessor :crop_height
attr_accessor :text_color
attr_accessor :shadow_color
DEFAULT_WIDTH = 200
DEFAULT_HEIGHT = 40
def initialize(id, icon = nil, text = nil, viewport = nil)
super(viewport)
@id = id
@selected = false
@text = text || get_text
@crop_width = nil
@crop_height = nil
@text_color = pbColor(:DARK_TEXT_MAIN_COLOR)
@shadow_color = pbColor(:DARK_TEXT_SHADOW_COLOR)
@text_padding = text_padding
if isDarkMode
@text_color, @shadow_color = @shadow_color, @text_color
end
@bg = IconSprite.new(0, 0, @viewport)
@bg.setBitmap(background_image)
@bg.z = self.z - 1
# Determine source bitmap
if icon.is_a?(String)
@icon_bitmap = AnimatedBitmap.new(icon)
bmp = @icon_bitmap.bitmap
elsif icon.is_a?(AnimatedBitmap)
@icon_bitmap = icon
bmp = icon.bitmap
elsif icon.is_a?(Bitmap)
bmp = icon
else
bmp = nil
end
# Create display bitmap
if bmp
self.bitmap = Bitmap.new(bmp.width, bmp.height)
self.bitmap.blt(0, 0, bmp, Rect.new(0, 0, bmp.width, bmp.height))
else
create_empty_bitmap
end
refresh
end
def text_alignment
return 0 #Left
#1 for centered
end
def text_padding
return 4
end
def viewport=(vp)
super(vp)
@bg&.dispose
@bg = IconSprite.new(0, 0, vp)
@bg.setBitmap(background_image)
@bg.z = self.z - 1
create_empty_bitmap if self.bitmap.nil?
refresh
end
def background_image
return ""
end
def create_empty_bitmap
self.bitmap = Bitmap.new(get_width, get_height)
pbSetSystemFont(self.bitmap)
# Draw background image directly onto bitmap
bg_path = background_image
if bg_path && bg_path != ""
bg_bmp = AnimatedBitmap.new(bg_path).bitmap
self.bitmap.stretch_blt(
Rect.new(0, 0, get_width, get_height),
bg_bmp,
Rect.new(0, 0, bg_bmp.width, bg_bmp.height)
)
end
end
def x=(value)
@x = value
super(value)
@bg.x = value if @bg
end
def y=(value)
@y = value
super(value)
@bg.y = value if @bg
end
def z=(value)
super(value)
@bg.z = value - 1 if @bg
end
def get_height
return DEFAULT_HEIGHT
end
def get_width
return DEFAULT_WIDTH
end
def get_default_image_path
return ""
end
def get_text
return ""
end
def dispose
dispose_graphics
super
end
def dispose_graphics
@icon_bitmap.dispose if @icon_bitmap
@bg.dispose if @bg
@icon_bitmap = nil
@bg =nil
end
def click
echoln "clicked #{@id}"
end
def hover
echoln "hovering over #{@id}"
end
def selected=(val)
oldsel = @selected
@selected = val
refresh if oldsel != val
end
def refresh
return unless self.bitmap
self.bitmap.clear
# Redraw background
bg_path = background_image
if bg_path && bg_path != ""
bg_bmp = AnimatedBitmap.new(bg_path).bitmap
self.bitmap.stretch_blt(
Rect.new(0, 0, self.bitmap.width, self.bitmap.height),
bg_bmp,
Rect.new(0, 0, bg_bmp.width, bg_bmp.height)
)
end
if @icon_bitmap
bmp = @icon_bitmap.bitmap
width = @crop_width || bmp.width
height = @crop_height || bmp.height
self.bitmap.blt(0, 0, bmp, Rect.new(0, 0, width, height))
end
draw_text if @text && @text != ""
end
def draw_text
return unless self.bitmap && @text && @text != ""
padding = @text_padding
max_width = self.bitmap.width - (padding * 2)
lines = wrap_text(@text, self.bitmap, max_width)
total_text_height = lines.length * LINE_HEIGHT
start_y = (self.bitmap.height - total_text_height) / 2
lines.each_with_index do |line, i|
y_pos = start_y + (LINE_HEIGHT * i)
self.bitmap.font.color = @shadow_color
self.bitmap.draw_text(padding + 1, y_pos + 1, max_width, LINE_HEIGHT, line, text_alignment)
self.bitmap.font.color = @text_color
self.bitmap.draw_text(padding, y_pos, max_width, LINE_HEIGHT, line, text_alignment)
end
end
end
@@ -0,0 +1,54 @@
def checkTrainerRematchChallenges
$Trainer.complete_challenge(:rematch_trainer)
end
class PokeBattle_Battler
def checkChallengesAfterTurn()
@battle.eachBattler do |battler|
if battler.pbOwnedByPlayer?
else
# # 1 HP left check
if battler.hp == 1
$Trainer.complete_challenge(:battle_enemy_1_hp)
end
end
end
end
def checkStatRaiseBattleChallenge(stat, increment)
$Trainer.complete_challenge(:battle_stat_boost)
$Trainer.complete_challenge(:battle_stat_boost_sharp) if increment >= 2
if statStageAtMax?(stat)
$Trainer.complete_challenge(:battle_stat_boost_max)
end
end
alias challenge_pbFlinch pbFlinch
def pbFlinch(_user = nil)
challenge_pbFlinch(_user)
if _user&.pbOwnedByPlayer?
$Trainer.complete_challenge(:battle_flinch)
end
end
end
class PokeBattle_Move
alias challenge_pbInflictHPDamage pbInflictHPDamage
def pbInflictHPDamage(target)
challenge_pbInflictHPDamage(target)
return if target.pbOwnedByPlayer?
# Not very effective 1 hit KO
if target.fainted? &&
target.damageState.initialHP == target.totalhp &&
Effectiveness.not_very_effective?(target.damageState.typeMod)
$Trainer.complete_challenge(:defeat_1_not_very_effective)
end
end
end
@@ -0,0 +1,37 @@
class PokemonTemp
attr_accessor :pokemon_is_weather_encounter
end
module PokeBattle_BattleCommon
def checkCatchChallenge(pokeball, battle, caught_pokemon)
#Caught in 1 try
if battle.balls_thrown == 0 #It's incremented after this method gets checked
$Trainer.complete_challenge(:catch_first_try)
end
#Catching at full health
if caught_pokemon.hp == caught_pokemon.totalhp
$Trainer.complete_challenge(:catch_full_health)
end
#Without receiving any damage
if battle.damage_received ==0
$Trainer.complete_challenge(:catch_no_damage)
end
if pokeball == :PREMIERBALL
$Trainer.complete_challenge(:catch_premierball)
end
if caught_pokemon.isFusion?
$Trainer.complete_challenge(:catch_fused)
end
if $PokemonBag.pbQuantity(pokeball) == 0
$Trainer.complete_challenge(:catch_last_pokeball)
end
if $PokemonTemp.pokemon_is_weather_encounter
$Trainer.complete_challenge(:catch_weather_encounter)
end
end
end
@@ -0,0 +1,49 @@
##
## Hooks for challenges about encountering overworld Pokemon
##
def all_same_pokemon?(pokemon_array)
species_array = pokemon_array.map {|pokemon| pokemon.species}
different_species = species_array.uniq
return different_species.length == 1
end
def all_different_pokemon?(pokemon_array)
species_array = pokemon_array.map {|pokemon| pokemon.species}
different_species = species_array.uniq
return different_species.length > 1
end
def checkEncounterChallenges(encountered_pokemon)
case encountered_pokemon.length
when 1
when 2
$Trainer.complete_challenge(:encounter_2_pokemon_at_once)
if all_different_pokemon?(encountered_pokemon)
$Trainer.complete_challenge(:encounter_2_different_pokemon_at_once)
elsif all_same_pokemon?(encountered_pokemon)
$Trainer.complete_challenge(:encounter_2_same_pokemon_at_once)
end
when 3
$Trainer.complete_challenge(:encounter_3_pokemon_at_once)
if all_different_pokemon?(encountered_pokemon)
$Trainer.complete_challenge(:encounter_3_different_pokemon_at_once)
elsif all_same_pokemon?(encountered_pokemon)
$Trainer.complete_challenge(:encounter_3_same_pokemon_at_once)
end
end
end
def checkWildFusePokemonChallenge(pokemon1,pokemon2)
$Trainer.complete_challenge(:fuse_wild_pokemon)
if $game_switches[SWITCH_WILD_FUSION_QUEST]
current_nb = pbGet(VAR_NB_WILD_FUSING_SEEN)
current_nb+=1
pbSet(VAR_NB_WILD_FUSING_SEEN,current_nb)
if current_nb <= 3
pbMEPlay("Register phone")
pbCallBub(3)
pbMessage(_INTL("Wild Fusion Study: {1} / 3",current_nb))
end
end
end
@@ -0,0 +1,41 @@
class PokemonTemp
attr_accessor :fuse_count_today
attr_accessor :unfuse_count_today
end
def checkFuseChallenges(head_pokemon, body_pokemon)
case $PokemonTemp.fuse_count_today
when 1
$Trainer.complete_challenge(:fuse_1_pokemon)
when 2
$Trainer.complete_challenge(:fuse_2_pokemon)
when 5
$Trainer.complete_challenge(:fuse_5_pokemon)
end
echoln head_pokemon.species
echoln body_pokemon.species
echoln head_pokemon.species == body_pokemon.species
if head_pokemon.species == body_pokemon.species
$Trainer.complete_challenge(:fuse_same_species)
end
species_data_head = GameData::Species.get(head_pokemon.species)
species_data_body = GameData::Species.get(body_pokemon.species)
for type in species_data_head&.types
if species_data_body.hasType?(type)
$Trainer.complete_challenge(:fuse_same_type)
end
end
end
def checkUnfuseChallenges(unfused_pokemon)
case $PokemonTemp.unfuse_count_today
when 1
$Trainer.complete_challenge(:unfuse_1_pokemon)
when 2
$Trainer.complete_challenge(:unfuse_2_pokemon)
when 5
$Trainer.complete_challenge(:unfuse_5_pokemon)
end
end
@@ -0,0 +1,154 @@
def openChallengeApp
pbFadeOutIn {
scene = PokemonChallenges_Scene.new
screen = PokemonChallenges_Screen.new(scene)
screen.pbStartScreen
# scene.pbRefresh
}
end
class Player
def add_challenge(challenge_id)
@challenges = {} unless @challenges
challenge_template = CHALLENGES[challenge_id]
return unless challenge_template
reward = pick_challenge_reward(challenge_template.category)
@challenges[challenge_id] = PlayerChallenge.new(challenge_id, reward)
end
def complete_challenge(challenge_id)
@challenges = {} unless @challenges
if @challenges.has_key?(challenge_id)
challenge = @challenges[challenge_id]
challenge.completed = true
@challenges[challenge_id] = challenge
$Trainer.nb_completed_challenges = 0 unless $Trainer.nb_completed_challenges
$Trainer.nb_completed_challenges += 1
pbSEPlay("challenge_complete")
end
end
def completed_challenge?(challenge_id)
if @challenges.has_key?(challenge_id)
return @challenges[challenge_id]
end
return false
end
def select_random_challenge(category)
candidates = CHALLENGES.select { |_k, v| v.category == category }.keys
return nil if candidates.empty?
return candidates.sample
end
def refresh_challenges()
@challenges = {} unless @challenges
@challenges.each do |challenge_id, challenge|
unless challenge.completed
remove_challenge(challenge.id)
end
end
categories = [:encounter, :battle, :catch, :fusion]
categories.each do |category|
unclaimed_challenges = listPlayerChallengesOfCategory(category)
if unclaimed_challenges.empty?
challenge_id = select_random_challenge(category)
add_challenge(challenge_id)
end
end
end
def listPlayerChallengesOfCategory(category)
challenges_list = []
@challenges.each do |challenge_id, challenge|
if challenge.category == category
challenges_list << challenge.id
end
end
return challenges_list
end
def remove_challenge(challenge_id)
@challenges.delete(challenge_id)
end
def clear_all_challenges
@challenges = {} unless @challenges
for id in @challenges
remove_challenge(id)
end
end
def pick_challenge_reward(category)
tier = rand(3) == 0 ? :TIER2 : :TIER1
items_list = []
case category
when :encounter
items_list = tier == :TIER1 ? CHALLENGE_ENCOUNTER_REWARDS_TIER1 : CHALLENGE_ENCOUNTER_REWARDS_TIER2
when :battle
items_list = tier == :TIER1 ? CHALLENGE_BATTLE_REWARDS_TIER1 : CHALLENGE_BATTLE_REWARDS_TIER2
when :catch
items_list = tier == :TIER1 ? CHALLENGE_CATCH_REWARDS_TIER1 : CHALLENGE_CATCH_REWARDS_TIER2
when :fusion
items_list = tier == :TIER1 ? CHALLENGE_FUSE_REWARDS_TIER1 : CHALLENGE_FUSE_REWARDS_TIER2
end
item = items_list.sample
if tier == :TIER1
quantity = rand(1..3)
else
quantity = 1
end
reward = []
for i in 1..quantity
reward << item
end
return reward
end
end
class PlayerChallenge
attr_reader :id
attr_reader :item_reward
attr_reader :template
attr_accessor :completed
def initialize(id, item_reward = [])
@id = id
@item_reward = item_reward
@template = CHALLENGES[id]
@completed = false
end
def description
return _INTL(@template.description)
end
def category
return _INTL(@template.category)
end
def money_reward
return _INTL(@template.money_reward)
end
end
class ChallengeTemplate
attr_reader :id, :description, :category, :money_reward
def initialize(id, description, category, money_reward)
@id = id
@description = description
@money_reward = money_reward
@category = category
end
end
CHALLENGES = {}
def define_challenge(id, description:, category:, money_reward:)
CHALLENGES[id] = ChallengeTemplate.new(id, description, category, money_reward)
end
@@ -0,0 +1,230 @@
CHALLENGE_ENCOUNTER_REWARDS_TIER1 = [:REPEL, :POTION, :ANTIDOTE]
CHALLENGE_ENCOUNTER_REWARDS_TIER2 = [:SUPERREPEL, :SUPERPOTION, :REVIVE, :DAWNSTONE, :DUSKSTONE]
CHALLENGE_BATTLE_REWARDS_TIER1 = [:XATTACK, :XDEFENSE, :ETHER, :FULLHEAL]
CHALLENGE_BATTLE_REWARDS_TIER2 = [:ELIXIR, :SITRUSBERRY]
CHALLENGE_CATCH_REWARDS_TIER1 = [:POKEBALL, :GREATBALL, :PREMIERBALL, :HEALBALL]
CHALLENGE_CATCH_REWARDS_TIER2 = [:ULTRABALL, :QUICKBALL, :DUSKBALL]
CHALLENGE_FUSE_REWARDS_TIER1 = [:DNASPLICERS, :DNAREVERSER]
CHALLENGE_FUSE_REWARDS_TIER2 = [:FUSIONBALL, :FUSIONREPEL]
########################
# Encounter challenges
########################
define_challenge :encounter_2_pokemon_at_once,
description: _INTL("Get into a battle against 2 wild Pokémon at once"),
category: :encounter,
money_reward: 200
define_challenge :encounter_3_pokemon_at_once,
description: _INTL("Get into a battle against 3 wild Pokémon at once"),
category: :encounter,
money_reward: 750
define_challenge :encounter_2_different_pokemon_at_once,
description: _INTL("Get into a battle against 2 different wild Pokémon at once"),
category: :encounter,
money_reward: 350
define_challenge :encounter_3_different_pokemon_at_once,
description: _INTL("Get into a battle against 3 different wild Pokémon at once"),
category: :encounter,
money_reward: 750
define_challenge :encounter_2_fused_pokemon_at_once,
description: _INTL("Get into a battle against 2 wild fused Pokémon at once"),
category: :encounter,
money_reward: 1000
define_challenge :encounter_2_same_pokemon_at_once,
description: _INTL("Get into a battle against 2 of the same Pokémon at once"),
category: :encounter,
money_reward: 350
define_challenge :encounter_3_same_pokemon_at_once,
description: _INTL("Get into a battle against 3 of the same species of Pokémon at once"),
category: :encounter,
money_reward: 750
define_challenge :encounter_offguard_any,
description: _INTL("Catch a Pokémon off guard"),
category: :encounter,
money_reward: 200
define_challenge :encounter_offguard_curious,
description: _INTL("Catch a curious Pokémon off guard"),
category: :encounter,
money_reward: 350
define_challenge :encounter_offguard_aggressive,
description: _INTL("Catch an aggressive Pokémon off guard"),
category: :encounter,
money_reward: 500
define_challenge :encounter_offguard_skittish,
description: _INTL("Catch a skittish Pokémon off guard"),
category: :encounter,
money_reward: 500
# define_challenge :wild_pokemon_chase_20_steps,
# description: _INTL("Get a wild Pokémon to chase you for 20 steps"),
# money_reward: 1000
########################
# Battle challenges
########################
define_challenge :defeat_1_not_very_effective,
description: _INTL("Defeat a Pokémon in one move using a not-very-effective move"),
category: :battle,
money_reward: 400
#
# define_challenge :defeat_1_indirect_damage,
# description: _INTL("Defeat a wild Pokémon without inflicting any direct damage"),
# money_reward: 500
#
define_challenge :battle_enemy_1_hp,
description: _INTL("Get an opposing Pokémon to exactly 1 HP"),
category: :battle,
money_reward: 1000
define_challenge :battle_flinch,
description: _INTL("Make your opponent flinch during a battle"),
category: :battle,
money_reward: 250
define_challenge :rematch_trainer,
description: _INTL("Rematch a trainer"),
category: :battle,
money_reward: 400
# define_challenge :battle_use_healing_item,
# description: _INTL("Use a healing item during a battle."),
# category: :battle,
# money_reward: 200
#
define_challenge :battle_stat_boost,
description: _INTL("Use a stat-boosting move or item during a battle"),
category: :battle,
money_reward: 300
define_challenge :battle_stat_boost_sharp,
description: _INTL("Sharply boost your Pokémon's stats in a battle"),
category: :battle,
money_reward: 500
define_challenge :battle_stat_boost_max,
description: _INTL("Max out one of your stats during a battle"),
category: :battle,
money_reward: 1000
#
# define_challenge :battle_berry,
# description: _INTL("Have a Pokémon use a berry during a battle"),
# category: :battle,
# money_reward: 300
# define_challenge :battle_one_hit_ko,
# description: _INTL("Land a One-Hit-KO move"),
# category: :fight,
# money_reward: 1000
#
########################
# Catching challenges
########################
define_challenge :catch_first_try,
description: _INTL("Catch a Pokémon on the first try"),
category: :catch,
money_reward: 400
define_challenge :catch_full_health,
description: _INTL("Catch a Pokémon at full health"),
category: :catch,
money_reward: 400
define_challenge :catch_no_damage,
description: _INTL("Catch a Pokémon without receiving any damage"),
category: :catch,
money_reward: 400
define_challenge :catch_premierball,
description: _INTL("Catch a Pokémon using a Premier Ball"),
category: :catch,
money_reward: 150
define_challenge :catch_fused,
description: _INTL("Catch a wild fused Pokémon"),
category: :catch,
money_reward: 300
define_challenge :catch_last_pokeball,
description: _INTL("Catch a Pokémon on your very last ball"),
category: :catch,
money_reward: 1000
define_challenge :catch_weather_encounter,
description: _INTL("Catch a Pokémon that appears in special weather conditions"),
category: :catch,
money_reward: 750
########################
# Fusing challenges
########################
#
define_challenge :fuse_same_species,
description: _INTL("Fuse two Pokémon of the same species together"),
category: :fusion,
money_reward: 500
define_challenge :fuse_same_type,
description: _INTL("Fuse two Pokémon that share the same type"),
category: :fusion,
money_reward: 500
define_challenge :fuse_5_pokemon,
description: _INTL("Fuse Pokémon 5 times"),
category: :fusion,
money_reward: 750
define_challenge :fuse_1_pokemon,
description: _INTL("Fuse a Pokémon"),
category: :fusion,
money_reward: 200
define_challenge :fuse_2_pokemon,
description: _INTL("Fuse Pokémon 2 times"),
category: :fusion,
money_reward: 300
define_challenge :unfuse_5_pokemon,
description: _INTL("Unfuse Pokémon 5 times"),
category: :fusion,
money_reward: 750
define_challenge :unfuse_2_pokemon,
description: _INTL("Unfuse Pokémon 2 times"),
category: :fusion,
money_reward: 300
define_challenge :unfuse_1_pokemon,
description: _INTL("Unfuse a Pokémon"),
category: :fusion,
money_reward: 200
define_challenge :fuse_wild_pokemon,
description: _INTL("Get two wild Pokémon to fuse before a battle"),
category: :fusion,
money_reward: 400
@@ -0,0 +1,156 @@
class PokemonChallenges_Scene
VISIBLE_BUTTONS = 3 # Number of buttons visible at once
Y_START = 41
Y_GAP = 108
BUTTONS_X = 60
def pbStartScene(challenges)
@challenges = []
@index = 0
@viewport = Viewport.new(0, 0, Graphics.width, Graphics.height)
@viewport.z = 99999
@sprites = {}
# Background
@sprites["background"] = IconSprite.new(0, 0, @viewport)
if isDarkMode
@sprites["background"].setBitmap("Graphics/Pictures/Pokegear/bg_dark")
else
@sprites["background"].setBitmap("Graphics/Pictures/Pokegear/bg")
end
@sprites["header"] = IconSprite.new(0, 0, @viewport)
@sprites["header"].setBitmap("Graphics/Pictures/Challenges/bg_header")
# Buttons
@buttons = []
$Trainer.challenges.keys.each_with_index do |challenge_id, i|
challenge = $Trainer.challenges[challenge_id]
next unless challenge
@challenges << challenge
btn = ChallengeButton.new(challenge, BUTTONS_X, Y_START + i * Y_GAP, @viewport)
@sprites["button#{i}"] = btn
@buttons << btn
end
displayTextElements
pbFadeInAndShow(@sprites) { pbUpdate }
end
def displayTextElements
Kernel.pbClearText
Kernel.pbDisplayText(_INTL("POKÉCHALLENGE"), 256,-8,99999, pbColor(:ORANGE), pbColor(:BROWN))
Kernel.pbDisplayText($Trainer.nb_completed_challenges.to_s, 450,-6,99999, pbColor(:GREEN), pbColor(:DARKGREEN))
if @buttons.empty?
showEmptyMessage
end
end
def showEmptyMessage
text_pos_x =240
text_pos_y =100
text_color = pbColor(:WHITE)
line_height = 36
Kernel.pbDisplayText(_INTL("You finished all your PokéChallenges!"), text_pos_x, text_pos_y,99999, text_color)
Kernel.pbDisplayText(_INTL("You'll be assigned new ones tomorrow."), text_pos_x, text_pos_y+line_height,99999, text_color)
end
def pbUpdate
# Update selection
@buttons.each_with_index do |btn, idx|
btn.selected = (idx == @index)
end
# Scroll calculation
scroll_offset = 0
if @index >= VISIBLE_BUTTONS
Kernel.pbClearText
@sprites["header"].visible=false
scroll_offset = (@index - VISIBLE_BUTTONS + 1) * Y_GAP
else
displayTextElements
@sprites["header"].visible=true
end
# Move buttons according to scroll
@buttons.each_with_index do |btn, idx|
btn.y = Y_START + idx * Y_GAP - scroll_offset
end
if @buttons.empty?
showEmptyMessage
end
pbUpdateSpriteHash(@sprites)
end
def pbScene
loop do
Graphics.update
Input.update
pbUpdate
if Input.trigger?(Input::BACK)
pbPlayCloseMenuSE
break
elsif Input.trigger?(Input::USE)
if @challenges.empty?
pbPlayCloseMenuSE
break
end
pbPlayDecisionSE
challenge = @challenges[@index]
if @buttons[@index].can_claim_reward
$Trainer.remove_challenge(challenge.id)
removeChallengeAt(@index)
pbSEPlay("GUI storage show party panel")
receiveChallengeReward(challenge)
pbUpdate
else
pbSEPlay("GUI sel buzzer",80)
pbMessage(_INTL("The reward can only be collected once you complete the challenge!"))
end
elsif Input.trigger?(Input::UP)
pbPlayCursorSE if @challenges.length > 1
@index -= 1
@index = @challenges.length - 1 if @index < 0
elsif Input.trigger?(Input::DOWN)
pbPlayCursorSE if @challenges.length > 1
@index += 1
@index = 0 if @index >= @challenges.length
end
end
end
def receiveChallengeReward(challenge)
money_reward = challenge.money_reward
item_reward = challenge.item_reward
quantity = item_reward.length
pbReceiveMoney(money_reward)
pbReceiveItem(item_reward[0], quantity)
$Trainer.money += money_reward
end
def challengeCompleted?(challenge_id)
return $Trainer.completed_challenge?(challenge_id)
end
#Removes the challenge button sprite
def removeChallengeAt(index)
btn = @buttons[index]
btn.dispose
@sprites.delete_if { |_, v| v == btn }
@buttons.delete_at(index)
@challenges.delete_at(index)
if @index >= @buttons.length
@index = @buttons.length - 1
end
@index = 0 if @index < 0
end
def pbEndScene
pbFadeOutAndHide(@sprites) { pbUpdate }
pbDisposeSpriteHash(@sprites)
Kernel.pbClearText
@viewport.dispose
end
end
@@ -0,0 +1,38 @@
#===============================================================================
#
#===============================================================================
class PokemonChallenges_Screen
def initialize(scene)
@scene = scene
end
def pbStartScreen
challenges = getChallengesList
$Trainer.pokenav.last_opened_challenges = pbGetTimeNow
@scene.pbStartScene(challenges)
@scene.pbScene
@scene.pbEndScene
end
def getChallengesList
last_opened = $Trainer.pokenav.last_opened_challenges
if shouldRefreshChallenges(last_opened)
$Trainer.refresh_challenges()
$PokemonTemp.fuse_count_today = 0
$PokemonTemp.unfuse_count_today = 0
end
return $Trainer.challenges.values
end
def shouldRefreshChallenges(last_opened_date)
return true if last_opened_date.nil?
#return Time.now.to_date > last_opened_date
return timeDateGreaterThan(pbGetTimeNow,last_opened_date)
#return pbGetTimeNow.to_date > last_opened_date #Replace Time.now by pbGetTimeNow everywhere to use in-game days for testing
end
end
@@ -0,0 +1,128 @@
#===============================================================================
#
#===============================================================================
class ChallengeButton < SpriteWrapper
attr_reader :challenge
attr_reader :selected
attr_reader :can_claim_reward
DESC_Y = 10
LINE_HEIGHT = 22
REWARD_LINE = DESC_Y + LINE_HEIGHT * 2
def initialize(challenge, x, y, viewport=nil)
super(viewport)
@challenge = challenge
@can_claim_reward = @challenge.completed
@selected = false
graphics_completed = "Graphics/Pictures/Challenges/button_complete_#{@challenge.category.to_s}"
graphics_incompleted = "Graphics/Pictures/Challenges/button_incomplete_#{@challenge.category.to_s}"
image_path = @can_claim_reward ? graphics_completed : graphics_incompleted
@cursor = AnimatedBitmap.new(image_path)
@contents = BitmapWrapper.new(@cursor.width, @cursor.height)
self.bitmap = @contents
self.x = x
self.y = y
pbSetSystemFont(self.bitmap)
refresh
end
def dispose
@cursor.dispose
@contents.dispose
super
end
def selected=(val)
oldsel = @selected
@selected = val
refresh if oldsel != val
end
def refresh
self.bitmap.clear
# Draw background
rect = Rect.new(0, 0, @cursor.width, @cursor.height / 2)
rect.y = @cursor.height / 2 if @selected
self.bitmap.blt(0, 0, @cursor.bitmap, rect)
# Text colors
text_color = @can_claim_reward ? Color.new(0,255,0) : Color.new(248,248,248)
shadow_color = Color.new(40,40,40)
# Description text
desc_lines = wrap_text(@challenge.description, @contents, @cursor.width - 40)[0, 2]
textpos = []
y_offset = DESC_Y
desc_lines.each do |line|
textpos << [line, 10, y_offset, 0, text_color, shadow_color, false]
y_offset += LINE_HEIGHT
end
reward_y = REWARD_LINE
reward_text = @can_claim_reward ? _INTL("Collect") : _INTL("Reward")
claim_color = @can_claim_reward ? Color.new(80, 220, 255) : Color.new(255, 215, 120)
textpos << [
"#{reward_text} $#{@challenge.money_reward}",
10, reward_y, 0,
claim_color, shadow_color, false
]
pbDrawTextPositions(self.bitmap, textpos)
# --------------------------------------------------
# Draw item reward icons
# --------------------------------------------------
return if !@challenge.item_reward || @challenge.item_reward.empty?
icon_x = 170 # starting X (adjust to taste)
icon_y = reward_y #- 4 # align with text nicely
icon_size = 48
icon_gap = 8
@challenge.item_reward.each do |item|
icon_path = GameData::Item.icon_filename(item)
next if !icon_path
icon = Bitmap.new(icon_path)
src_rect = Rect.new(0, 0, icon.width, icon.height)
self.bitmap.stretch_blt(
Rect.new(icon_x, icon_y, icon_size, icon_size),
icon,
src_rect
)
icon.dispose
icon_x += icon_size + icon_gap
end
end
# Helper method to wrap text into multiple lines
def wrap_text(text, bitmap, max_width)
words = text.split(" ")
lines = []
line = ""
words.each do |word|
test_line = line.empty? ? word : "#{line} #{word}"
if bitmap.text_size(test_line).width > max_width
lines << line
line = word
else
line = test_line
end
end
lines << line unless line.empty?
return lines
end
end
@@ -0,0 +1,209 @@
class ContactsAppScene < PokeNavAppScene
INFO_TEXT_Y = 270
def cursor_x_offset
return 16
end
def cursor_y_offset
return -16
end
def header_name
return _INTL("Trainers")
end
def cursor_path
return "Graphics/Pictures/Pokegear/Trainers/icon_button_static"
end
def header_path
return "Graphics/Pictures/Pokegear/Trainers/bg_header_trainers"
end
def display_mode
return :LIST
end
def x_gap
return 74;
end
def y_gap
return 48;
end
def columns
return 1
end
def visible_rows
return 3
end
def start_x
return 40;
end
def start_y
return 80;
end
def pbStartScene(screen)
@screen = screen
buttons = []
@trainers = []
@contacts_list = screen.list_contacts
@contacts_list.each do |location, trainers_list|
next unless location
buttons << ContactsAppLocationButton.new(location, nil, location)
next unless trainers_list && trainers_list.size > 0
trainers_list.each do |trainer|
next unless trainer
next unless screen.can_be_listed(trainer)
trainerClassName = GameData::TrainerType.get(trainer.trainerType).name
trainerName = MessageTypes.getFromHash(MessageTypes::TrainerNames, trainer.trainerName) || trainer.trainerName
trainer_name = _INTL("{1} {2}", trainerClassName, trainerName)
@trainers << trainer.id
button = ContactsAppTrainerButton.new(trainer.id, trainer.overworld_sprite, trainer_name)
$Trainer.pokenav.viewed_trainers = [] unless $Trainer&.pokenav&.viewed_trainers
button.set_trade_available(trainer.can_trade?)
button.set_new(!$Trainer.pokenav.viewed_trainers.include?(trainer.id))
buttons << button
end
end
super(buttons)
@index = 1
scroll_to_current_location
displayTextElements
@buttons[@index].hover
end
def updateInput
cols = columns
total = @buttons.length
move_delay = 2
if Input.trigger?(Input::BACK)
pbPlayCloseMenuSE
@exiting = true
return
elsif Input.trigger?(Input::USE)
pbPlayDecisionSE
click(@buttons[@index]&.id)
elsif Input.repeat?(Input::LEFT)
prev_location = find_previous_location_index
move_to_index(prev_location+1)
pbWait(move_delay)
elsif Input.repeat?(Input::RIGHT)
next_location = find_next_location_index
move_to_index(next_location+1)
pbWait(move_delay)
elsif Input.repeat?(Input::UP)
move_index(-cols)
pbWait(move_delay)
elsif Input.repeat?(Input::DOWN)
move_index(cols)
pbWait(move_delay)
end
end
def scroll_to_current_location
current_location_name = getMapName($game_map.map_id)
for i in @index..@buttons.length-1
if @buttons[i].is_a?(ContactsAppLocationButton) && @buttons[i].id == current_location_name
new_index = i+1 #next index for the first trainer in that location
move_to_index(new_index)
end
end
end
def find_next_location_index
for i in @index..@buttons.length-1
if @buttons[i].is_a?(ContactsAppLocationButton)
return i
end
end
return @buttons.length-1
end
def find_previous_location_index
found_current_location = false
for i in @index.downto(0)
if @buttons[i].is_a?(ContactsAppLocationButton)
return i if found_current_location
found_current_location = true
end
end
return 1
end
def move_index(delta)
return if @buttons.empty?
pbPlayCursorSE
new_index = (@index + delta) % @buttons.length
if @buttons[new_index].is_a?(ContactsAppLocationButton)
new_index = new_index + delta
end
new_index = @buttons.length - 1 if new_index < 0
@index = new_index
hover(@buttons[@index]&.id)
end
def createCursor
super
@sprites["cursor"].x=16
@sprites["cursor"].y=-32
end
def click(button_id)
super
@screen.view_trainer_page(button_id, @trainers)
# cmd_info = _INTL("Info")
# cmd_team = _INTL("View Team")
# cmd_cancel = _INTL("Cancel")
# commands = [cmd_info, cmd_team, cmd_cancel]
# choice = pbMessage(_INTL("What would you like to do?"), commands, commands.size)
# case commands[choice]
# when cmd_info
# @screen.view_trainer_page(button_id, @trainers)
# when cmd_team
# @screen.view_trainer_team(button_id)
# end
end
def layoutButtons
return if @exiting
current_row = @index
scroll_row = [current_row - visible_rows + 1, 0].max
y_positions = []
cumulative_y = 0
@buttons.each do |btn|
y_positions << cumulative_y
cumulative_y += btn.get_height + (btn.respond_to?(:bottom_margin) ? btn.bottom_margin : 0)
end
scroll_pixels = y_positions[[scroll_row, @buttons.length - 1].min]
@buttons.each_with_index do |btn, i|
btn.x = start_x
btn.y = start_y + y_positions[i] - scroll_pixels
btn.visible = (btn.y >= start_y - btn.get_height && btn.y <= Graphics.height)
btn.selected = (i == @index)
end
updateCursor
updateHeader(scroll_row)
end
def pbUpdate
super
@buttons.each { |btn| btn.update if btn.respond_to?(:update) }
end
def hover(button_id)
super
end
end
@@ -0,0 +1,99 @@
#===============================================================================
#
#===============================================================================
class ContactsAppScreen
def initialize(scene)
@scene = scene
end
UNLISTABLE_TRAINER_TYPES = [:TEAM_AQUA_GRUNT_M, :TEAM_AQUA_GRUNT_F,
:TEAM_MAGMA_GRUNT_M, :TEAM_MAGMA_GRUNT_F,
:TEAM_MAGMAQUA_GRUNT_M, :TEAM_MAGMAQUA_GRUNT_F,
:TEAM_AQUA_EXEC_M, :TEAM_AQUA_EXEC_F,
:TEAM_MAGMA_EXEC_M, :TEAM_MAGMA_EXEC_F,
:TEAM_AQUA_BOSS, :TEAM_MAGMA_BOSS,
:ADVENTURER_KANTO, :ADVENTURER_JOHTO,
:ADVENTURER_SINNOH, :ADVENTURER_UNOVA,
:ADVENTURER_KALOS, :ADVENTURER_ALOLA,
]
def pbStartScreen(main_menu_scene, screen)
@main_menu_scene = main_menu_scene
@scene.pbStartScene(self)
@scene.pbScene
@scene.pbEndScene
@screen = screen
end
def list_contacts
contacts_list_by_location = {}
$PokemonGlobal.battledTrainers = {} unless $PokemonGlobal.battledTrainers
$PokemonGlobal.battledTrainers.each do |id, trainer|
next unless can_be_listed(trainer)
if trainer.favorite
contacts_list_by_location[_INTL("Favorites")] ||= []
contacts_list_by_location[_INTL("Favorites")] << trainer
else
location = trainer.location
contacts_list_by_location[location] ||= []
contacts_list_by_location[location] << trainer
end
end
contacts_list_by_location.each_value do |trainer_array|
trainer_array.sort_by! { |t| t.trainerName }
end
# Move Favorites to front
favorites = contacts_list_by_location.delete(_INTL("Favorites"))
if favorites
contacts_list_by_location = { _INTL("Favorites") => favorites }.merge(contacts_list_by_location)
end
return contacts_list_by_location
end
def can_be_listed(trainer)
trainerType = trainer.trainerType
return false if UNLISTABLE_TRAINER_TYPES.include?(trainerType)
return true
end
def view_trainer_page(trainer_id, trainers_list)
trainer= getRebattledTrainerFromKey(trainer_id)
if trainer
pbFadeOutIn {
scene = ContactsAppInfoPageScene.new
screen = ContactsAppInfoPageScreen.new
screen.pbStartScreen(scene, trainer, trainers_list)
}
else
pbSEPlay("buzzer", 80)
pbWait(4)
end
end
def view_trainer_team(trainer_id)
trainer= getRebattledTrainerFromKey(trainer_id)
if trainer
team = trainer.currentTeam
pbFadeOutIn {
scene = PokemonSummary_Scene.new
screen = PokemonSummaryScreen.new(scene)
screen.pbStartScreen(team,0, false)
}
else
pbSEPlay("buzzer", 80)
pbWait(4)
end
end
def pbSummary(list, index)
visibleSprites = pbFadeOutAndHide(@sprites) { pbUpdate }
scene = PokemonSummary_Scene.new
screen = PokemonSummaryScreen.new(scene)
@sprites["list"].index = screen.pbStartScreen(list, index ,false)
pbFadeInAndShow(@sprites, visibleSprites) { pbUpdate }
end
end
@@ -0,0 +1,335 @@
class ContactsAppInfoPageScene < PokeNavAppScene
attr_accessor :trainer
SPRITE_POSITION_X = 375
SPRITE_POSITION_Y = 230
TITLE_TEXT_X = 380
TITLE_TEXT_Y = 50
INFO_HEADER_X = 40
INFO_TEXT_X = 60
INFO_TEXT_START_Y = 50
INFO_TEXT_GAP = 40
INFO_HEADER_GAP = 30
FRIENDSHIP_ICON = "Graphics/Pictures/Pokegear/Trainers/friendship"
FRIENDSHIP_ICON_GAP = 4
ICON_SIZE = 16
FRIENDSHIP_ICON_START_X = 40
FRIENDSHIP_ICON_START_Y = 20
INFO_TEXT_LINES_PER_PAGE = 2
def header_name
return _INTL("Trainers")
end
def cursor_path
return "Graphics/Pictures/Pokeradar/icon_button"
end
def header_path
return "Graphics/Pictures/Pokegear/bg_header_trainers"
end
def bg_path
return "Graphics/Pictures/Pokegear/Trainers/bg_summary"
end
def display_mode
return :LIST
end
def x_gap
return 75;
end
def y_gap
return 64;
end
def columns
return 1
end
def visible_rows
return 3
end
def start_x
return 40;
end
def start_y
return 80;
end
def initialize
super
@value_color_base = Color.new(24, 112, 216)
@value_color_shadow = Color.new(136, 168, 208)
if isDarkMode
@value_color_base, @value_color_shadow = @value_color_shadow, @value_color_base
end
@info_text_page = 0
@info_text_pages = []
end
def pbStartScene(screen, trainer)
echoln "start"
@screen = screen
buttons = []
@trainer = trainer
super(buttons)
if @trainer
showTrainerSprite
showTrainerInfo
showFriendshipIcons
else
pbEndScene
end
end
def showTrainerSprite
if @trainer.id == BATTLED_TRAINER_RIVAL_KEY
bitmap = generate_front_trainer_sprite_bitmap_from_appearance($Trainer.rival_appearance)
@sprites["trainer"] = IconSprite.new(0, 0, @viewport)
@sprites["trainer"].setBitmapDirectly(bitmap)
else
trainerFile = GameData::TrainerType.front_sprite_filename(@trainer.trainerType)
@sprites["trainer"] = IconSprite.new(0, 0, @viewport)
@sprites["trainer"].setBitmap(trainerFile)
end
if @sprites["trainer"].bitmap &&
@sprites["trainer"].bitmap.width > @sprites["trainer"].bitmap.height * 2
@sprites["trainer"].src_rect.x = 0
@sprites["trainer"].src_rect.width = @sprites["trainer"].bitmap.width / 5
end
@sprites["trainer"].ox = @sprites["trainer"].src_rect.width / 2
@sprites["trainer"].oy = @sprites["trainer"].bitmap.height
@sprites["trainer"].z = 50
@sprites["trainer"].x = SPRITE_POSITION_X
@sprites["trainer"].y = SPRITE_POSITION_Y
end
def showFriendshipIcons
@friendship_icons&.each(&:dispose)
@friendship_icons = []
(@trainer.friendship_level || 0).times do |i|
sprite = IconSprite.new(0, 0, @viewport)
sprite.setBitmap(FRIENDSHIP_ICON)
sprite.x = FRIENDSHIP_ICON_START_X + i * (ICON_SIZE + FRIENDSHIP_ICON_GAP)
sprite.y = FRIENDSHIP_ICON_START_Y
sprite.z = 100002
@friendship_icons << sprite
@sprites["friendship_icon_#{i}"] = sprite
end
end
def showTrainerInfo
Kernel.pbClearText
showHeaderName
trainerClassName = GameData::TrainerType.get(@trainer.trainerType).name
trainerName = MessageTypes.getFromHash(MessageTypes::TrainerNames, @trainer.trainerName) || @trainer.trainerName
trainer_name = _INTL("{1} {2}", trainerClassName, trainerName)
level_sum = 0
if @trainer.currentTeam.size > 0
@trainer.currentTeam.each do |pokemon|
level_sum += pokemon.level
end
average_level = (level_sum / @trainer.currentTeam.length).round
else
average_level = _INTL("N/A")
end
location = @trainer.location
if @trainer.id == BATTLED_TRAINER_RIVAL_KEY
favorite_type = _INTL("All")
elsif @trainer.id == BATTLED_TRAINER_WALLY_KEY
favorite_type = _INTL("Unknown")
else
favorite_type = (GameData::Type.try_get(@trainer.favorite_type)&.name) || _INTL("Unknown")
end
Kernel.pbDisplayText(trainer_name, TITLE_TEXT_X, TITLE_TEXT_Y, 999999, @text_color_base, @text_color_shadow)
current_y = INFO_TEXT_START_Y
displayText(_INTL("Location:"), INFO_HEADER_X, current_y)
current_y += INFO_HEADER_GAP
displayValue(location, INFO_TEXT_X, current_y)
current_y += INFO_TEXT_GAP
displayText(_INTL("Average team level:"), INFO_HEADER_X, current_y)
current_y += INFO_HEADER_GAP
displayValue(average_level.to_s, INFO_TEXT_X, current_y)
current_y += INFO_TEXT_GAP
displayText(_INTL("Favorite type:"), INFO_HEADER_X, current_y)
current_y += INFO_HEADER_GAP
displayValue(favorite_type, INFO_TEXT_X, current_y)
trainer_data = GameData::Trainer.try_get(@trainer.trainerType, @trainer.trainerName, 0)
current_y += INFO_TEXT_GAP * 1.5
if @trainer.previous_random_events
action = getBestMatchingPreviousRandomEvent(trainer_data, @trainer.previous_random_events)
if action
case action.eventType
when :CATCH
action_text = _INTL("Recently caught a {1}.",
GameData::Species.get(action.caught_pokemon).name)
when :EVOLVE
action_text = _INTL("Recently evolved their {1}.",
GameData::Species.get(action.evolved_pokemon).name)
when :FUSE
action_text = _INTL("Recently fused their {1} and {2}\.",
GameData::Species.get(action.fusion_head_pokemon).name,
GameData::Species.get(action.fusion_body_pokemon).name)
when :UNFUSE
action_text = _INTL("Recently unfused their {1}.",
GameData::Species.get(action.unfused_pokemon).name)
when :REVERSE
action_text = _INTL("Recently reversed their {1}.",
GameData::Species.get(action.reversed_pokemon).name)
end
end
end
displayInfoText(current_y, trainer_data, action_text)
end
def displayInfoText(current_y, trainer_data, action_text = nil)
infoText = trainer_data&.infoText
if infoText || action_text
if infoText
full_text = infoText.gsub("___", "")
full_text = full_text.gsub("<PLAYER_NAME>", $Trainer.name)
full_text = "\"#{full_text}\"" unless full_text.empty?
end
max_width = SPRITE_POSITION_X - INFO_HEADER_X - 20
temp_bitmap = Bitmap.new(1, 1)
all_lines = full_text ? wrap_text(full_text, temp_bitmap, max_width) : []
temp_bitmap.dispose
@info_text_pages = all_lines.each_slice(INFO_TEXT_LINES_PER_PAGE).to_a
#insert action page as first
@info_text_pages.unshift([action_text]) if action_text
@info_text_page = @info_text_page.clamp(0, [@info_text_pages.size - 1, 0].max)
page_lines = @info_text_pages[@info_text_page] || []
page_lines.each do |line|
displayText(line, INFO_HEADER_X, current_y)
current_y += INFO_TEXT_GAP
end
if @info_text_pages.size > 1
indicator = "#{@info_text_page + 1} / #{@info_text_pages.size}"
displayText(indicator, Graphics.width - 80, 340)
end
else
@info_text_pages = []
@info_text_page = 0
end
end
def rebuildInfoTextPage(all_lines)
@info_text_pages = all_lines.each_slice(INFO_TEXT_LINES_PER_PAGE).to_a
@info_text_page = @info_text_page.clamp(0, [@info_text_pages.size - 1, 0].max)
end
def updateInput
if Input.trigger?(Input::UP)
change_trainer(-1)
pbSEPlay("GUI naming tab swap start")
elsif Input.trigger?(Input::DOWN)
change_trainer(1)
pbSEPlay("GUI naming tab swap start")
elsif Input.trigger?(Input::LEFT)
if @info_text_pages.size > 1 && @info_text_page > 0
@info_text_page -= 1
pbSEPlay("GUI naming tab swap start")
refresh_info_text
end
elsif Input.trigger?(Input::RIGHT)
if @info_text_pages.size > 1 && @info_text_page < @info_text_pages.size - 1
@info_text_page += 1
pbSEPlay("GUI naming tab swap start")
refresh_info_text
end
elsif Input.trigger?(Input::BACK)
pbPlayCloseMenuSE
@exiting = true
return
elsif Input.trigger?(Input::USE)
pbPlayDecisionSE
click(@buttons[@index]&.id)
end
end
# Re-renders only the info text area without rebuilding the whole scene.
def refresh_info_text
Kernel.pbClearText
showHeaderName
showTrainerInfo
end
def change_trainer(index_delta)
@info_text_page = 0
@info_text_pages = []
@screen.change_trainer(index_delta)
@sprites["trainer"]&.dispose
@friendship_icons&.each_with_index { |_, i| @sprites.delete("friendship_icon_#{i}") }
Kernel.pbClearText()
showTrainerSprite
showTrainerInfo
showFriendshipIcons
end
def displayText(text, x_position, y_position)
Kernel.pbDisplayText(text, x_position, y_position, nil, @text_color_base, @text_color_shadow, 3)
end
def displayValue(text, x_position, y_position)
Kernel.pbDisplayText(text, x_position, y_position, nil, @value_color_base, @value_color_shadow, 3)
end
# def Kernel.pbDisplayText(message,xposition,yposition,z=nil, baseColor=nil, shadowColor=nil,alignment=2)
def createCursor
return if $PokemonTemp.pokeradar
super
end
def click(button_id)
super
echoln @trainer.id
cmd_team = _INTL("View Team")
cmd_cancel = _INTL("Cancel")
commands = []
commands << cmd_team if @trainer.currentTeam.size > 0
commands << cmd_cancel
#choice = pbMessage(nil, commands, commands.size)
choice = pbShowCommands(nil, commands, commands.size, 0, 330,200)
case commands[choice]
when cmd_team
Kernel.pbClearText()
@screen.view_trainer_team(@trainer.id)
showTrainerInfo
end
end
def hover(button_id)
super
end
def pbEndScene
super
#Re-write the header name since going back to trainers list which has already been initialized
Kernel.pbDisplayText(header_name, Graphics.width/2 , HEADER_HEIGHT)
end
end
@@ -0,0 +1,54 @@
class ContactsAppInfoPageScreen
def pbStartScreen(scene, trainer, trainers_list)
@trainers_list = trainers_list
@scene = scene
@index = get_current_index(trainer.id)
@scene.pbStartScene(self, trainer)
@scene.pbScene
@scene.pbEndScene
updateStatus
end
def updateStatus
trainer_id = @trainers_list[@index]
unless $Trainer.pokenav.viewed_trainers.include?(trainer_id)
$Trainer.pokenav.viewed_trainers << trainer_id
end
end
def view_trainer_team(trainer_id)
trainer = getRebattledTrainerFromKey(trainer_id)
if trainer
team = trainer.currentTeam
pbFadeOutIn {
scene = PokemonSummary_Scene.new
screen = PokemonSummaryScreen.new(scene)
screen.pbStartScreen(team, 0, false)
}
else
pbSEPlay("buzzer", 80)
pbWait(4)
end
end
def get_current_index(current_trainer_id)
@trainers_list.each_with_index do |trainer_id, i|
if trainer_id == current_trainer_id
return i
end
end
return 0
end
def change_trainer(delta)
new_index = @index + delta
new_index = @trainers_list.length - 1 if new_index >= @trainers_list.length
new_index = 0 if new_index < 0
@index = new_index
new_trainer_id = @trainers_list[new_index]
@scene.trainer = getRebattledTrainerFromKey(new_trainer_id)
updateStatus
end
end
@@ -0,0 +1,19 @@
class ContactsAppLocationButton < PokenavButton
BOTTOM_MARGIN = 8 # tune this
def get_height
return 40
end
def get_width
return 200
end
def get_text
return @id.to_s
end
def bottom_margin
return BOTTOM_MARGIN
end
end
@@ -0,0 +1,168 @@
class ContactsAppTrainerButton < PokenavButton
IMAGE_TEXT_GAP = 128
DEFAULT_SPRITE_PATH = "000"
IMAGE_X_OFFSET = 32
SOURCE_IMAGE_Y_CROP = 24
ICON_SIZE = 24
ICON_X_MARGIN = 8
ICON_GAP = 4
TRADE_AVAILABLE_ICON = "Graphics/Pictures/Pokegear/Trainers/tradeIcon"
IS_NEW_ICON = "Graphics/Pictures/Pokegear/Trainers/dialogIcon"
ICON_X_OFFSET = -20
FADE_SPEED = 16
def get_width
return Graphics.width-72
end
def get_height
return 56
end
def background_image
if isDarkMode
return "Graphics/Pictures/Pokegear/Trainers/trainer_list_button_dark.png"
else
return "Graphics/Pictures/Pokegear/Trainers/trainer_list_button.png"
end
end
def set_trade_available(value)
@is_trade_available = value
create_icon_sprites
end
def set_new(value)
@is_new = value
create_icon_sprites
end
def initialize(id, image = nil, text = nil, viewport = nil)
@image_path = image
@image_path = DEFAULT_SPRITE_PATH if @image_path.nil?
super(id, nil, text, viewport)
@is_trade_available = false
@is_new = false
refresh
end
def viewport=(vp)
super(vp)
create_image_sprite
create_icon_sprites
end
def create_image_sprite
return unless @image_path && self.viewport
@image_sprite = IconSprite.new(0, 0, self.viewport)
if @id == BATTLED_TRAINER_RIVAL_KEY
rival_bitmap = AnimatedBitmap.new(getBaseOverworldSpriteFilename())
rival_bitmap.bitmap = generateNPCClothedBitmapStatic($Trainer.rival_appearance)
@image_sprite.setBitmapDirectly(rival_bitmap)
else
@image_sprite.setBitmap("Graphics/Characters/#{@image_path}")
end
frame_w = (@image_sprite.bitmap.width / 4).round
frame_h = (@image_sprite.bitmap.height / 4).round
@image_sprite.src_rect.width = frame_w
@image_sprite.src_rect.height = [frame_h - SOURCE_IMAGE_Y_CROP, get_height].min
@image_sprite.src_rect.x = 16
@image_sprite.src_rect.y = 16
@image_sprite.z = self.z + 1
end
def create_icon_sprites
return unless self.viewport
@icon_sprites&.each(&:dispose)
@icon_sprites = []
icons = []
icons << TRADE_AVAILABLE_ICON if @is_trade_available
icons << IS_NEW_ICON if @is_new
icons.each do |path|
sprite = IconSprite.new(0, 0, self.viewport)
sprite.setBitmap(path)
sprite.z = self.z + 1
@icon_sprites << sprite
end
update_icon_positions
end
def update_icon_positions
return unless @icon_sprites
right_edge = (self.x || 0) + get_width - ICON_X_MARGIN
@icon_sprites.each_with_index do |sprite, i|
sprite.x = right_edge - ICON_SIZE - (i * (ICON_SIZE + ICON_GAP)) + ICON_X_OFFSET
sprite.y = (self.y || 0) + (get_height - ICON_SIZE) / 2
sprite.visible = self.visible
end
end
def hover
if @is_new
$Trainer.pokenav.viewed_trainers << @id
@is_new = false
@fading_new_icon = @icon_sprites.last # IS_NEW_ICON is last in array
end
super
end
def update
return unless @fading_new_icon
@fading_new_icon.opacity -= FADE_SPEED
if @fading_new_icon.opacity <= 0
@fading_new_icon.dispose
@icon_sprites.delete(@fading_new_icon)
@fading_new_icon = nil
update_icon_positions # reflow remaining icons
end
end
def x=(value)
super
@image_sprite&.x = value + IMAGE_X_OFFSET
update_icon_positions
end
def y=(value)
super
frame_h = @image_sprite ? @image_sprite.src_rect.height : 0
@image_sprite&.y = value + (get_height - frame_h) / 2 - 8
update_icon_positions
end
def visible=(value)
super
@image_sprite&.visible = value
@icon_sprites&.each { |s| s.visible = value }
end
def dispose
@image_sprite&.dispose
@icon_sprites&.each(&:dispose)
super
end
def draw_text(x_offset = 0)
return unless self.bitmap && @text && @text != ""
frame_w = @image_sprite ? @image_sprite.src_rect.width : 0
text_x = IMAGE_TEXT_GAP
max_width = self.bitmap.width - text_x
lines = wrap_text(@text, self.bitmap, max_width)
lines.each_with_index do |line, i|
y_pos = (get_height / 2) - (LINE_HEIGHT * lines.size / 2) + (LINE_HEIGHT * i)
self.bitmap.font.color = @shadow_color
self.bitmap.draw_text(text_x + 1, y_pos + 1, max_width, LINE_HEIGHT, line)
self.bitmap.font.color = @text_color
self.bitmap.draw_text(text_x, y_pos, max_width, LINE_HEIGHT, line)
end
end
end
@@ -0,0 +1,107 @@
class FusionQuizAppScene < PokeNavAppScene
attr_accessor :playing
attr_accessor :difficulty
def initialize
super
@playing = false
end
def y_gap
return 64;
end
def start_x
return Graphics.width-200;
end
def pbStartScene(buttons = nil)
$game_system.bgm_memorize
super(buttons)
pbBGMPlay("game_corner")
displayTextElements
end
def bg_path
if @playing
if @difficulty == :ADVANCED
return "Graphics/Pictures/Pokegear/FusionQuiz/bg_play_advanced"
else
return "Graphics/Pictures/Pokegear/FusionQuiz/bg_play"
end
else
return "Graphics/Pictures/Pokegear/FusionQuiz/bg_menu"
end
end
def cursor_path
return "Graphics/Pictures/Pokegear/FusionQuiz/cursor"
end
def header_name
return _INTL("Who's That Fusion!")
end
def header_path
return "Graphics/Pictures/Pokegear/FusionQuiz/bg_header_quiz"
end
def click(button_id)
case button_id
when "play"
@selected = :play
@exiting = true
when "score"
@selected = :score
@exiting = true
else
pbPlayCloseMenuSE
@selected = :exit
@exiting = true
end
end
def updateInput
if Input.trigger?(Input::BACK)
pbPlayCloseMenuSE
@selected = :exit
@exiting = true
return
end
super
end
def selected_action
return @selected
end
def updateBackground
echoln bg_path
@sprites["bg"].setBitmap(bg_path) unless @sprites["bg"].disposed?
end
def pbEndSceneKeepBg
@exiting = true
sprites_without_bg = @sprites.reject { |k, _| ["bg", "background", "header"].include?(k) }
pbFadeOutAndHide(sprites_without_bg) { pbUpdate }
pbDisposeSpriteHash(sprites_without_bg)
@buttons.each(&:dispose)
Kernel.pbClearText
showHeaderName
end
def disposeBg
@sprites["bg"]&.dispose
@sprites["background"]&.dispose
@sprites["header"]&.dispose
@viewport&.dispose
end
def pbEndScene
echoln "ENDING"
$game_system.bgm_stop
$game_system.bgm_restore
super
end
end
@@ -0,0 +1,183 @@
class FusionQuizAppScreen
def initialize(scene)
@scene = scene
end
POINTS_TO_UNLOCK_MODES = {
:regular_3_rounds => 0,
:regular_5_rounds => 2000,
:regular_10_rounds => 4000,
:advanced_3_rounds => 5000,
:advanced_5_rounds => 12000,
:advanced_10_rounds => 16000,
}
def pbStartScreen(main_menu_scene, screen)
@main_menu_scene = main_menu_scene
@screen = screen
#Possible modes:
# :regular_3_rounds
# :regular_5_rounds
# :regular_10_rounds
# :advanced_3_rounds
# :advanced_5_rounds
# :advanced_10_rounds
$Trainer&.pokenav&.fusion_quiz_unlocked_modes = [:regular_3_rounds] unless $Trainer&.pokenav&.fusion_quiz_unlocked_modes
loop do
btn_play = FusionQuizMenuButton.new("play", nil, _INTL("Play"))
btn_score = FusionQuizMenuButton.new("score", nil, _INTL("Score"))
btn_close = FusionQuizMenuButton.new("exit", nil, _INTL("Exit"))
@scene.pbStartScene([btn_play, btn_score, btn_close])
@scene.pbScene
case @scene.selected_action
when :play
@scene.pbEndSceneKeepBg
launch_quiz
@scene.playing = false
@scene.disposeBg
when :score
@scene.pbEndSceneKeepBg
show_high_score
@scene.disposeBg
when :exit, nil
@scene.pbEndScene
break
end
end
end
def launch_quiz
difficulty = prompt_difficulty
high_score = pbGet(VAR_STAT_FUSION_QUIZ_HIGHEST_SCORE)
@scene.difficulty = difficulty
if difficulty
nb_rounds = prompt_nb_rounds(difficulty)
if nb_rounds > 0
@scene.playing = true
@scene.updateBackground
quiz = FusionQuiz.new(difficulty)
quiz.silhouette_color = Color.new(0, 0, 0, 200)
quiz.windowed = false
if difficulty == :ADVANCED
quiz.picture_offset_x = -30
quiz.picture_offset_y = 32
else
quiz.picture_offset_x = -40
quiz.picture_offset_y = 32
end
quiz.start_quiz(nb_rounds)
unless quiz.player_abandonned
score = quiz.get_score
if score > high_score
pbMEPlay("Level Up")
pbMessage(_INTL("You beat your previous high score!", score))
end
unlock_new_modes(score)
end
end
end
end
def get_mode_name(mode_id)
case mode_id
when :regular_3_rounds
return _INTL("Regular (3 Rounds)")
when :regular_5_rounds
return _INTL("Regular (5 Rounds)")
when :regular_10_rounds
return _INTL("Regular (10 Rounds)")
when :advanced_3_rounds
return _INTL("Advanced (3 Rounds)")
when :advanced_5_rounds
return _INTL("Advanced (5 Rounds)")
when :advanced_10_rounds
return _INTL("Advanced (10 Rounds)")
end
end
def unlock_new_modes(score)
POINTS_TO_UNLOCK_MODES.keys.each do |mode_id|
points_to_unlock = POINTS_TO_UNLOCK_MODES[mode_id]
next if $Trainer&.pokenav&.fusion_quiz_unlocked_modes&.include?(mode_id)
if score >= points_to_unlock
$Trainer&.pokenav&.fusion_quiz_unlocked_modes = [:regular_3_rounds] unless $Trainer&.pokenav&.fusion_quiz_unlocked_modes
$Trainer&.pokenav&.fusion_quiz_unlocked_modes << mode_id
pbSEPlay("itemlevel", 80)
pbMessage(_INTL("Unlocked a new difficulty: \\C[3]{1}",get_mode_name(mode_id)))
end
end
end
def prompt_difficulty
advanced_difficulties = [:advanced_3_rounds, :advanced_5_rounds, :advanced_10_rounds]
echoln $Trainer&.pokenav&.fusion_quiz_unlocked_modes
cmd_regular = _INTL("Regular")
cmd_advanced = _INTL("Advanced")
cmd_cancel = _INTL("Cancel")
options = []
options << cmd_regular
options << cmd_advanced if ($Trainer&.pokenav&.fusion_quiz_unlocked_modes & advanced_difficulties)&.any?
options << cmd_cancel
choice = pbMessage(
_INTL("Choose a difficulty:"),
options,3
)
case options[choice]
when cmd_regular
return :REGULAR
when cmd_advanced
return :ADVANCED
else
return nil
end
end
def prompt_nb_rounds(difficulty)
options = []
cmd_3_rounds = _INTL("3 Rounds")
cmd_5_rounds = _INTL("5 Rounds")
cmd_10_rounds = _INTL("10 Rounds")
cmd_cancel = _INTL("Cancel")
case difficulty
when :ADVANCED
options << cmd_3_rounds if $Trainer&.pokenav&.fusion_quiz_unlocked_modes&.include?(:advanced_3_rounds)
options << cmd_5_rounds if $Trainer&.pokenav&.fusion_quiz_unlocked_modes&.include?(:advanced_5_rounds)
options << cmd_10_rounds if $Trainer&.pokenav&.fusion_quiz_unlocked_modes&.include?(:advanced_10_rounds)
when :REGULAR
options << cmd_3_rounds if $Trainer&.pokenav&.fusion_quiz_unlocked_modes&.include?(:regular_3_rounds)
options << cmd_5_rounds if $Trainer&.pokenav&.fusion_quiz_unlocked_modes&.include?(:regular_5_rounds)
options << cmd_10_rounds if $Trainer&.pokenav&.fusion_quiz_unlocked_modes&.include?(:regular_10_rounds)
end
options << cmd_cancel
choice = pbMessage(
_INTL("Choose the number of rounds:"),
options,4
)
case options[choice]
when cmd_3_rounds
nb_rounds = 3
when cmd_5_rounds
nb_rounds = 5
when cmd_10_rounds
nb_rounds = 10
else
nb_rounds = 0
end
return nb_rounds
end
def show_high_score
high = pbGet(VAR_STAT_FUSION_QUIZ_HIGHEST_SCORE)
total = pbGet(VAR_STAT_FUSION_QUIZ_TOTAL_PTS)
times = pbGet(VAR_STAT_FUSION_QUIZ_NB_TIMES)
pbMessage(_INTL("High Score: {1} pts", high))
pbMessage(_INTL("Total Points: {1}\\nGames Played: {2}", total, times))
end
end
@@ -0,0 +1,35 @@
class FusionQuizMenuButton < PokenavButton
IMAGE_TEXT_GAP = 128
DEFAULT_SPRITE_PATH = "000"
IMAGE_X_OFFSET = 32
SOURCE_IMAGE_Y_CROP = 24
ICON_SIZE = 24
ICON_X_MARGIN = 8
ICON_GAP = 4
def initialize(id, icon = nil, text = nil, viewport = nil)
super
@text_color = pbColor(:LIGHT_TEXT_MAIN_COLOR)
@shadow_color = pbColor(:LIGHT_TEXT_SHADOW_COLOR)
end
def get_width
return 180
end
def get_height
return 52
end
def text_padding
return 40
end
def text_alignment
return 1 #Centered
end
def background_image
return "Graphics/Pictures/Pokegear/FusionQuiz/icon_button"
end
end
@@ -0,0 +1,176 @@
class Trainer
attr_accessor :pokenav
def install_app(app_id)
@pokenav = Pokenav.new unless $Trainer.pokenav
@pokenav.install_app(app_id)
end
end
class Pokenav
attr_accessor :installed_apps
attr_accessor :last_opened_challenges #date
attr_accessor :darkMode
attr_accessor :viewed_trainers
attr_accessor :last_opened_quest_mode
attr_accessor :fusion_quiz_unlocked_modes
AVAILABLE_APPS = {
# Starting apps
:QUESTS => "Quests",
:MAP => "Town Map",
:DAYNIGHT => "Toggle Dark/Light",
:REARRANGE => "Rearrange",
# Unlockable apps
:CONTACTS => "Trainers", # obtained after rematch quest TODO
:JUKEBOX => "Jukebox", # obtained at devon corp
:WEATHER => "Weather Map", # obtained at TV Mauville
:POKERADAR => "PokéRadar", # given by the rival somewhere?
:POKECHALLENGE => "PokéChallenge", #Obtained at Slateport fan club
:BOXLINK => "Box Link",
:FUSIONQUIZ => "Who's That Fusion?",
:BERRYDEX => "BerryDex",
#To implement
# Statistics (shows you a bunch of statstics. nb of times you fused, nb of steps taken, etc.)
#
#Other ideas
# Guess That Fusion -> same game as in pif 1, but in app form
# Slot machine
# Weather
# Secret Bases map (a map that lists all the secret bases)
# Something to check your Pokemon's EVs / IVs
# Daycare app that tells you the status of your eggs
}
def self.app_name(app_id)
return _INTL(AVAILABLE_APPS[app_id] || app_id.to_s)
end
def initialize
@installed_apps = [:MAP, :QUESTS, :DAYNIGHT, :REARRANGE]
@last_opened_challenges = nil
@viewed_trainers = []
@exiting = false
@last_opened_quest_mode = :MAP
end
def install_app(app_id)
return unless Pokenav::AVAILABLE_APPS.keys.include?(app_id)
initialize unless @installed_apps
@installed_apps << app_id unless @installed_apps.include?(app_id)
app_name = Pokenav.app_name(app_id)
pbMEPlay("match_call")
pbMessage(_INTL("The \\C[3]{1} App\\C[0] was installed in the PokéNav!", app_name))
end
def has_app(app_id)
return @installed_apps.include?(app_id)
end
end
class PokemonPokegearScreen
def update_commands
commands = []
$Trainer.pokenav&.installed_apps.each do |app|
commands << [app.to_s, Pokenav.app_name(app)]
end
return commands
end
def pbStartScreen
$Trainer.pokenav = Pokenav.new unless $Trainer.pokenav
commands = update_commands
@scene.pbStartScene(commands)
loop do
cmd = @scene.pbScene
commands = update_commands #in case they're reordered
chosen = commands[cmd][0].to_sym
if cmd < 0
break
elsif chosen == :MAP
pbWeatherMap
# pbShowMap(-1, false)
elsif chosen == :JUKEBOX
pbFadeOutIn {
scene = PokemonJukebox_Scene.new
screen = PokemonJukeboxScreen.new(scene)
screen.pbStartScreen
}
elsif chosen == :QUESTS
pbQuestlog()
elsif chosen == :CONTACTS
openContactsApp
next
elsif chosen == :WEATHER
pbWeatherMap
elsif chosen == :POKECHALLENGE
openChallengeApp
elsif chosen == :POKERADAR
openPokeRadarApp
elsif chosen == :REARRANGE
@scene.rearrange_order
elsif chosen == :DAYNIGHT
toggleDarkMode
elsif chosen == :BOXLINK
openBoxLinkApp
elsif chosen == :FUSIONQUIZ
openGuessThatFusionApp
elsif chosen == :BERRYDEX
pbBerryDex
# elsif cmdPhone>=0 && cmd==cmdPhone
# pbFadeOutIn {
# PokemonPhoneScene.new.start
# }
end
end
@scene.pbEndScene
end
def openBoxLinkApp
blacklisted_maps = []
if blacklisted_maps.include?($game_map.map_id)
Kernel.pbMessage(_INTL("There doesn't seem to be any network coverage here..."))
else
pbFadeOutIn {
scene = PokemonStorageScene.new
screen = PokemonStorageScreen.new(scene, $PokemonStorage)
screen.pbStartScreen(0) # Boot PC in organize mode
}
end
end
def openContactsApp
pbFadeOutIn {
scene = ContactsAppScene.new
screen = ContactsAppScreen.new(scene)
screen.pbStartScreen(@scene,screen)
}
end
def openPokeRadarApp
pbFadeOutIn {
scene = PokeRadarAppScene.new
screen = PokeRadarAppScreen.new(scene)
screen.pbStartScreen(@scene)
}
end
def openGuessThatFusionApp
pbFadeOutIn {
scene = FusionQuizAppScene.new
screen = FusionQuizAppScreen.new(scene)
screen.pbStartScreen(@scene,screen)
}
end
def toggleDarkMode
pbSEPlay("GUI storage show party panel")
$Trainer.pokenav.darkMode = false if $Trainer.pokenav.darkMode.nil?
$Trainer.pokenav.darkMode = !$Trainer.pokenav.darkMode
@scene.reloadBackground
end
end
@@ -0,0 +1,520 @@
# todo:
# When all pokemon in the route are seen, can "scan" for species
# - spawns a pokemon of that species nearby with notice behavior to flee
class PokeRadarAppScene < PokeNavAppScene
INFO_TEXT_Y = 270
def header_name
return _INTL("PokéRadar")
end
def cursor_path
return "Graphics/Pictures/Pokeradar/icon_button"
end
def header_path
return "Graphics/Pictures/Pokeradar/bg_header"
end
def display_mode
return :GRID
end
def x_gap
return 75;
end
def y_gap
return 50;
end
def columns
return 6
end
def visible_rows
return 3
end
def start_x
return 40;
end
def start_y
return 80;
end
def pbStartScene(main_menu_scene)
@unseenPokemon = [] unless @unseenPokemon
@seenPokemon = [] unless @seenPokemon
@pokenav_main_menu_scene = main_menu_scene
if $PokemonTemp.pokeradar
buttons = showCurrentScanningTarget
super(buttons)
else
buttons = showWildPokemonList
super(buttons)
showSelectedPokemonInfo
end
showHeaderInfo
end
def showSelectedPokemonInfo
hover(@buttons[@index]&.id)
if @unseenPokemon.empty? && @seenPokemon.empty?
showEmpty
end
end
def showHeaderInfo
showBattery
showHeaderName
showAreaName
showWeatherIcon
end
def showWildPokemonList
@encounter_type = $PokemonEncounters.encounter_type
@weatherEncounters = $PokemonEncounters.list_weather_encounters_species(@encounter_type)
@unseenPokemon = listPokemonInCurrentRoute(@encounter_type, false, true, true)
@seenPokemon = listPokemonInCurrentRoute(@encounter_type, true, false, true)
buttons = []
@seenPokemon.each do |pokemon_species|
icon_path = pbCheckPokemonIconFiles(pokemon_species)
bmp = load_bitmap(icon_path, false)
button = PokenavButton.new(pokemon_species, bmp)
button.crop_width = button.icon_bitmap.bitmap.width / 2
button.refresh
buttons << button if button
end
@unseenPokemon.each do |pokemon_species|
icon_path = pbCheckPokemonIconFiles(pokemon_species)
bmp = load_bitmap(icon_path, true)
button = PokenavButton.new(pokemon_species, bmp)
button.crop_width = button.icon_bitmap.bitmap.width / 2
button.refresh
buttons << button
end
return buttons
end
def createCursor
return if $PokemonTemp.pokeradar
super
end
def showEmpty
Kernel.pbDisplayText(_INTL("No wild Pokémon found nearby."), Graphics.width / 2, Graphics.height / 2, nil, @text_color_base, @text_color_shadow)
Kernel.pbDisplayText(_INTL("Stand in tall grass to start scanning."), Graphics.width / 2,( Graphics.height / 2)+32, nil, @text_color_base, @text_color_shadow)
end
def showCurrentScanningTarget
scanningPokemon = $PokemonTemp.pokeradar[0]
icon_path = pbCheckPokemonIconFiles(scanningPokemon)
bmp = load_bitmap(icon_path, false)
button = PokenavButton.new(scanningPokemon, bmp)
button.refresh
species_name = GameData::Species.get(scanningPokemon).name
Kernel.pbDisplayText(_INTL("Currently scanning for {1}.", species_name), Graphics.width / 2, 200, 500000, @text_color_base, @text_color_shadow)
Kernel.pbDisplayText(_INTL("Current chain: {1}.", $PokemonTemp.pokeradar[2]), Graphics.width / 2, 230, 500000, @text_color_base, @text_color_shadow)
return [button]
end
def load_bitmap(path, dark = false)
return nil unless path && path != ""
bmp = Bitmap.new(path)
if dark
darken_bitmap(bmp, 220) # strength 0255
end
return AnimatedBitmap.from_bitmap(bmp)
end
def darken_bitmap(bmp, amount)
for x in 0...bmp.width
for y in 0...bmp.height
pixel = bmp.get_pixel(x, y)
next if pixel.alpha == 0
factor = (255 - amount) / 255.0
r = (pixel.red * factor).to_i
g = (pixel.green * factor).to_i
b = (pixel.blue * factor).to_i
bmp.set_pixel(x, y, Color.new(r, g, b, pixel.alpha))
end
end
end
#[rareness, species, minLevel, maxLevel]
def get_encounter(species,include_weather=true)
for encounter in $PokemonEncounters.listPossibleEncounters(@encounter_type, include_weather)
if encounter[1] == species
return encounter
end
end
return nil
end
def get_weather_encounter(species)
for encounter in $PokemonEncounters.list_weather_encounters(@encounter_type)
if encounter[1] == species
return encounter
end
end
return nil
end
def get_energy_for_scan(species)
encounter = get_encounter(species)
if @weatherEncounters.include?(species)
base_rareness = encounter[0]
rareness =calculate_weather_encounter_rareness(encounter,base_rareness)
else
rareness = encounter[0]
end
energy = (110 - rareness) * 5
(energy / 50.0).round * 50
end
def get_rarity_flavor_text(species)
encounter = get_encounter(species,false)
weather_encounter = get_weather_encounter(species)
return unless encounter || weather_encounter
if (encounter && encounter[1] == species) || (weather_encounter && weather_encounter[1] == species)
base_rareness =0
base_rareness = encounter[0] if encounter
base_rareness = weather_encounter[0] if weather_encounter
if @weatherEncounters.include?(species)
rareness = calculate_weather_encounter_rareness(weather_encounter,base_rareness)
else
rareness = base_rareness
end
if rareness < 5
text = _INTL("Very rare")
elsif rareness < 10
text = _INTL("Rare")
elsif rareness < 25
text = _INTL("Uncommon")
elsif rareness < 40
text = _INTL("Common")
else
text = _INTL("Very Common")
end
return text
end
end
def calculate_weather_encounter_rareness(encounter,base_rareness)
map_weather = $game_weather.current_weather[$game_map.map_id]
if map_weather
weather_intensity = map_weather[1]
weather_intensity = 0 if !weather_intensity
chance_of_weather_encounter = $PokemonEncounters.weather_encounter_chance(weather_intensity)
weather_rareness = encounter[0]
return weather_rareness * (chance_of_weather_encounter.to_f / 100) * (base_rareness/100)
end
return 0
end
def click(button_id)
species = button_id
if $PokemonTemp.pokeradar
click_stop_scan(species)
else
if @seenPokemon.include?(species)
click_seen(species)
else
click_unseen
end
end
super
end
def hover(button_id)
return if $PokemonTemp.pokeradar
if @seenPokemon.include?(button_id)
hover_seen(button_id)
else
hover_unseen
end
super
end
def click_stop_scan(species)
species_name = GameData::Species.get(species).name
options = []
cmd_stop_scan = _INTL("Stop Scanning")
cmd_cancel = _INTL("Cancel")
options << cmd_stop_scan
options << cmd_cancel
Kernel.pbClearText()
showHeaderInfo
chosen = pbMessage(_INTL("You are currently scanning for {1}.", species_name), options, options.length)
case options[chosen]
when cmd_stop_scan
$PokemonTemp.pokeradar = nil
pbEndScene
pbStartScene(@pokenav_main_menu_scene)
else
showCurrentScanningTarget
return
end
end
def click_seen(species)
options = []
cmd_scan = _INTL("Scan")
cmd_cancel = _INTL("Cancel")
options << cmd_scan
options << cmd_cancel
Kernel.pbClearText()
showHeaderInfo
chosen = pbMessage(_INTL("What would you like to do?"), options, options.length)
case options[chosen]
when cmd_scan
energy_needed = get_energy_for_scan(species)
if Settings::POKERADAR_BATTERY_STEPS - $PokemonGlobal.pokeradarBattery >= energy_needed
$PokemonGlobal.pokeradarBattery += energy_needed
displayTextElements
@exiting = true
pbEndScene
@pokenav_main_menu_scene.exiting = true
@pokenav_main_menu_scene.pbEndScene
encounter = get_encounter(species)
min_level = encounter[2]
max_level = encounter[3]
level = rand(min_level..max_level)
pbWait(16)
pbMessage(_INTL("Scanning for {1}...\\wtnp[5]", GameData::Species.get(species).name))
possible_tiles = getTerrainTilesNearPlayer(getTerrainType, 3)
position = possible_tiles.sample
if position
set_pokeradar_data(species, level, position)
spawn_pokeradar_pokemon(species, level)
else
pbPokeRadarCancel
pbMessage(_INTL("The Pokéradar scan failed... Try again somewhere else"))
end
else
pbPokeRadarCancel
Kernel.pbClearText()
showHeaderInfo
pbMessage(_INTL("The battery is not charged enough for this scan!"))
showSelectedPokemonInfo
end
else
showSelectedPokemonInfo
return
end
end
def click_unseen
return unless @unseenPokemon.any?
Kernel.pbClearText()
showHeaderInfo
pbMessage(_INTL("You need to encounter the Pokémon before you can scan for it."))
hover_unseen
end
def hover_seen(species)
displayTextElements
pokemon_name = GameData::Species.get(species).name
Kernel.pbDisplayText(pokemon_name, Graphics.width / 2, INFO_TEXT_Y, 99999, @text_color_base, @text_color_shadow)
Kernel.pbDisplayText(get_rarity_flavor_text(species), Graphics.width / 2, INFO_TEXT_Y + 30, 99999, @text_color_base, @text_color_shadow)
Kernel.pbDisplayText(_INTL("Battery for scan: {1}", get_energy_for_scan(species)), Graphics.width / 2, INFO_TEXT_Y + 60, 99999, @text_color_base, @text_color_shadow)
end
def hover_unseen()
return unless @unseenPokemon.any?
displayTextElements
pokemon_name = _INTL("Unknown Pokémon")
Kernel.pbDisplayText(pokemon_name, Graphics.width / 2, INFO_TEXT_Y, 999999, @text_color_base, @text_color_shadow)
end
def displayTextElements
super
showBattery
showAreaName
end
def showBattery
$PokemonGlobal.pokeradarBattery = Settings::POKERADAR_BATTERY_STEPS unless $PokemonGlobal.pokeradarBattery
battery_power = Settings::POKERADAR_BATTERY_STEPS - $PokemonGlobal.pokeradarBattery
Kernel.pbDisplayText(_INTL("{1}/1000", battery_power), 450, HEADER_HEIGHT,nil, @text_color_base, @text_color_shadow)
end
def showAreaName
map_name = getMapName($game_map.map_id)
text = _INTL("{1}", map_name)
encounter_type = get_encounter_type_name
if encounter_type && !encounter_type.empty?
text += _INTL(" ({1})", encounter_type)
end
Kernel.pbDisplayText(text, Graphics.width / 2, 40,nil, @text_color_base, @text_color_shadow)
end
def showWeatherIcon
icon_path = get_current_weather_icon
return unless icon_path
@sprites["weather"] = IconSprite.new(400, 48, @viewport)
@sprites["weather"].setBitmap(icon_path)
@sprites["weather"].z = 100000
end
def get_encounter_type_name
encounter_type = $PokemonEncounters.encounter_type
case encounter_type
when :Land
return _INTL("Grass")
when :Land1
return _INTL("Clovers")
when :Land2
return _INTL("Dry Grass")
when :Land3
return _INTL("Flowers")
when :LandMorning, :LandDay, :LandNight
return _INTL("Grass")
when :WaterMorning, :WaterDay, :WaterNight
return _INTL("Water")
when :TallGrass
return _INTL("Tall Grass")
else
return encounter_type.to_s
end
end
end
def getTerrainType
encounter_type = $PokemonEncounters.encounter_type
case encounter_type
when :Land, :Land1, :Land2, :Land3, :LandMorning, :LandDay, :LandNight, :TallGrass
return :Grass
else
return encounter_type
end
end
def continue_pokeradar_app_chain()
pbWait(12)
species = $PokemonTemp.pokeradar[0]
level = $PokemonTemp.pokeradar[1]
possible_tiles = getTerrainTilesNearPlayer(getTerrainType, 3)
position = possible_tiles.sample
if position
set_pokeradar_data(species, level, position)
spawn_pokeradar_pokemon(species, level)
else
pbPokeRadarCancel
pbMessage(_INTL("The Pokéradar scan failed... Try again somewhere else"))
end
end
def update_pokeradar_overworld_ui
if $PokemonTemp.pokeradar
current_chain = $PokemonTemp.pokeradar[2]
return unless current_chain >= 1
species = $PokemonTemp.pokeradar[0]
$PokemonTemp.pokeradar_ui.dispose if $PokemonTemp.pokeradar_ui
$PokemonTemp.pokeradar_ui = PokeRadar_UI.new([species], [], [])
if current_chain >= 40
$PokemonTemp.pokeradar_ui.set_text(_INTL("PokéRadar Chain: {1}", current_chain), 72, 12, pbColor(:GREEN), pbColor(:DARKGREEN))
else
$PokemonTemp.pokeradar_ui.set_text(_INTL("PokéRadar Chain: {1}", current_chain), 72, 12)
end
else
$PokemonTemp.pokeradar_ui.dispose if $PokemonTemp.pokeradar_ui
end
end
def spawn_pokeradar_pokemon(species, level)
max_attempts = 50
return unless species && level
pbWait(20)
playAnimation(Settings::POKERADAR_LIGHT_ANIMATION_RED_ID, $game_player.x, $game_player.y)
pbWait(10)
spawned_events = nil
radius = 4
max_radius = 8
max_attempts.times do |current_attempt|
radius += 1 if current_attempt > 0 && current_attempt % 10 == 0
radius += (current_attempt / 10).floor
radius = [radius, max_radius].min
spawned_events = spawn_ow_pokemon(species, level, 1, radius)
break if spawned_events&.length.to_i > 0
end
if spawned_events&.length.to_i > 0
event = spawned_events[0]
grass = $PokemonTemp&.pokeradar[3]
if grass && grass[3] == 2
pbSEPlay("shiny", 60)
playAnimation(Settings::SPARKLE_SHORT_ANIMATION_ID, event.x, event.y)
event.make_shiny
end
#todo: MAYBE increase the move frequency past a certain chain number (50?) to make it more difficult
event.behavior_roaming = :look_around_player
event.behavior_noticed = event.pokemon.shiny? ? :curious : :flee
event.update_movement_type
event.turn_away_from_player
playAnimation(Settings::POKERADAR_LIGHT_ANIMATION_RED_ID, event.x, event.y)
else
echoln "failed to spawn after #{max_attempts} attempts"
pbPokeRadarCancel
pbMessage(_INTL("The Pokéradar scan failed... Try again somewhere else"))
end
update_pokeradar_overworld_ui
end
# Reusing the old pokeradar mechanics for chaining
def set_pokeradar_data(species, level, position)
x = position[0]
y = position[1]
existing_chain = ($PokemonTemp.pokeradar && $PokemonTemp.pokeradar[2]) ? $PokemonTemp.pokeradar[2] : 0
$PokemonTemp.pokeradar = [0, 0, 0, []] unless $PokemonTemp.pokeradar
$PokemonTemp.pokeradar[0] = species
$PokemonTemp.pokeradar[1] = level
$PokemonTemp.pokeradar[2] = existing_chain
$PokemonTemp.pokeradar[3] = [x, y, 0, determine_shininess]
end
def determine_shininess
chain_length = ($PokemonTemp.pokeradar && $PokemonTemp.pokeradar[2]) ? $PokemonTemp.pokeradar[2] : 0
rarity = (rand(100) < 25) ? 1 : 0
if chain_length > 0
capped_chain = [chain_length, 40].min
shiny_threshold = [(65536 / Settings::SHINY_POKEMON_CHANCE) - capped_chain * 200, 200].max
shiny_divisor = 0xFFFF / shiny_threshold
shiny_roll = rand(65536) / shiny_divisor
rarity = 2 if shiny_roll == 0
end
return rarity
end
Events.onMapChange += proc { |_sender, e|
pbPokeRadarCancel
}
@@ -0,0 +1,17 @@
#===============================================================================
#
#===============================================================================
class PokeRadarAppScreen
def initialize(scene)
@scene = scene
end
def pbStartScreen(main_menu_scene)
@scene.pbStartScene(main_menu_scene)
@scene.pbScene
@scene.pbEndScene
end
end
@@ -0,0 +1,74 @@
class TrainerStatistics
attr_accessor :pokecenter_heals
attr_accessor :pokemon_contests_participated_total
attr_accessor :pokemon_contests_participated_category
attr_accessor :pokemon_contests_participated_rank
attr_accessor :pokemon_contests_participated_category_rank
attr_accessor :pokemon_contests_won_total
attr_accessor :pokemon_contests_won_category
attr_accessor :pokemon_contests_won_rank
attr_accessor :pokemon_contests_won_category_rank
attr_accessor :berries_planted
attr_accessor :bike_hops_distance
def initialize
@pokecenter_heals = 0
@nb_trades = 0
@nb_battles_won = 0
@nb_battles_lost = 0
@nb_pokemon_defeated = 0
@nb_pokemon_surprised = 0
@berries_planted = 0
@bike_hops_distance = 0
initializeContestStats
end
def initializeContestStats
@pokemon_contests_participated_total ||= 0
@pokemon_contests_participated_category ||= [0,0,0,0,0]
@pokemon_contests_participated_rank ||= [0,0,0,0]
@pokemon_contests_participated_category_rank ||= [[0,0,0,0],[0,0,0,0],[0,0,0,0],[0,0,0,0],[0,0,0,0]]
@pokemon_contests_won_total ||= 0
@pokemon_contests_won_category ||= [0,0,0,0,0]
@pokemon_contests_won_rank ||= [0,0,0,0]
@pokemon_contests_won_category_rank ||= [[0,0,0,0],[0,0,0,0],[0,0,0,0],[0,0,0,0],[0,0,0,0]]
end
def contest_stats_initialized?
!@pokemon_contests_participated_total.nil?
end
def incr_nb_pokemon_surprised
@nb_pokemon_surprised = 1 unless @nb_pokemon_surprised
@nb_pokemon_surprised += 1
end
def incr_nb_pokemon_defeated
@nb_pokemon_defeated = 1 unless @nb_pokemon_defeated
@nb_pokemon_defeated += 1
end
def incr_nb_trades
@nb_trades = 1 unless @nb_trades
@nb_trades += 1
end
def incr_nb_battles_won
@nb_battles_won = 1 unless @nb_battles_won
@nb_battles_won += 1
end
def incr_nb_battles_lost
@nb_battles_lost = 1 unless @nb_battles_lost
@nb_battles_lost += 1
end
def incr_nb_bike_hops_steps
@bike_hops_distance = 1 unless @bike_hops_distance
@bike_hops_distance += 1
end
def nb_fusions
return $game_variables[VAR_STAT_NB_FUSIONS]
end
end