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