update 6.7

This commit is contained in:
chardub
2025-09-28 15:53:01 -04:00
parent ef5e023ae0
commit 318ff90d8d
696 changed files with 111759 additions and 198230 deletions

View File

@@ -0,0 +1,190 @@
class PokemonBoxArrow < SpriteWrapper
attr_accessor :cursormode
alias _multiSelect_PokemonBoxArrow_initialize initialize
def initialize(*args)
_multiSelect_PokemonBoxArrow_initialize(*args)
@cursormode = "default"
@handsprite.addBitmap("point1m", "Graphics/Pictures/Storage/cursor_point_1_m")
@handsprite.addBitmap("point2m", "Graphics/Pictures/Storage/cursor_point_2_m")
@handsprite.addBitmap("grabm", "Graphics/Pictures/Storage/cursor_grab_m")
@handsprite.addBitmap("fistm", "Graphics/Pictures/Storage/cursor_fist_m")
@multiheldpkmn = []
end
alias _multiSelect_PokemonBoxArrow_dispose dispose
def dispose
_multiSelect_PokemonBoxArrow_dispose
@multiheldpkmn.each { |pkmn| pkmn.dispose }
end
alias _multiSelect_PokemonBoxArrow_visible_eq visible=
def visible=(value)
_multiSelect_PokemonBoxArrow_visible_eq(value)
multiHeldPokemon.each { |pkmn| pkmn.visible = value }
end
alias _multiSelect_PokemonBoxArrow_color_eq color=
def color=(value)
_multiSelect_PokemonBoxArrow_color_eq(value)
multiHeldPokemon.each { |pkmn| pkmn.color = value }
end
alias _multiSelect_PokemonBoxArrow_x_eq x=
def x=(value)
_multiSelect_PokemonBoxArrow_x_eq(value)
multiHeldPokemon.each { |pkmn| pkmn.x = self.x + (pkmn.heldox * 48) } if holdingMulti?
end
alias _multiSelect_PokemonBoxArrow_y_eq y=
def y=(value)
_multiSelect_PokemonBoxArrow_y_eq(value)
multiHeldPokemon.each { |pkmn| pkmn.y = self.y + 16 + (pkmn.heldoy * 48) } if holdingMulti?
end
def setSprite(sprite)
if holdingSingle?
@heldpkmn = sprite
@heldpkmn.viewport = self.viewport if @heldpkmn
@heldpkmn.z = 1 if @heldpkmn
@holding = false if !@heldpkmn && @multiheldpkmn.length == 0
self.z = 2
end
end
def setSprites(sprites)
if holdingMulti?
@multiheldpkmn = sprites
for pkmn in @multiheldpkmn
pkmn.viewport = self.viewport
pkmn.z = 1
end
@holding = false if !@heldpkmn && @multiheldpkmn.length == 0
self.z = 2
end
end
alias _multiSelect_PokemonBoxArrow_deleteSprite deleteSprite
def deleteSprite
_multiSelect_PokemonBoxArrow_deleteSprite
@multiheldpkmn.each { |pkmn| pkmn.dispose }
@multiheldpkmn = []
end
def grabImmediate(sprite)
@grabbingState = 0
@holding = true
@heldpkmn = sprite
@heldpkmn.viewport = self.viewport
@heldpkmn.z = 1
self.z = 2
self.x = @spriteX
self.y = @spriteY
end
def holdingMulti?
return @multiheldpkmn.length > 0 && @holding
end
def heldPokemon
@heldpkmn = nil if @heldpkmn && @heldpkmn.disposed?
@holding = false if !@heldpkmn && @multiheldpkmn.length == 0
return @heldpkmn
end
def getModeSprites
case @cursormode
when "quickswap"
return ["point1q", "point2q", "grabq", "fistq"]
when "multiselect"
return ["point1m", "point2m", "grabm", "fistm"]
else
return ["point1", "point2", "grab", "fist"]
end
end
def update
@updating = true
super
heldpkmn = heldPokemon
heldpkmn.update if heldpkmn
multiheldpkmn = multiHeldPokemon
multiheldpkmn.each { |pkmn| pkmn.update }
modeSprites = getModeSprites
@handsprite.update
@holding = false if !heldpkmn && multiheldpkmn.length == 0
if @grabbingState > 0
if @grabbingState <= 4 * Graphics.frame_rate / 20
@handsprite.changeBitmap(modeSprites[2]) # grab
self.y = @spriteY + 4.0 * @grabbingState * 20 / Graphics.frame_rate
@grabbingState += 1
elsif @grabbingState <= 8 * Graphics.frame_rate / 20
@holding = true
@handsprite.changeBitmap(modeSprites[3]) # fist
self.y = @spriteY + 4 * (8 * Graphics.frame_rate / 20 - @grabbingState) * 20 / Graphics.frame_rate
@grabbingState += 1
else
@grabbingState = 0
end
elsif @placingState > 0
if @placingState <= 4 * Graphics.frame_rate / 20
@handsprite.changeBitmap(modeSprites[3]) # fist
self.y = @spriteY + 4.0 * @placingState * 20 / Graphics.frame_rate
@placingState += 1
elsif @placingState <= 8 * Graphics.frame_rate / 20
@holding = false
@heldpkmn = nil
@multiheldpkmn = []
@handsprite.changeBitmap(modeSprites[2]) # grab
self.y = @spriteY + 4 * (8 * Graphics.frame_rate / 20 - @placingState) * 20 / Graphics.frame_rate
@placingState += 1
else
@placingState = 0
end
elsif holdingSingle? || holdingMulti?
@handsprite.changeBitmap(modeSprites[3]) # fist
else
self.x = @spriteX
self.y = @spriteY
if @frame < Graphics.frame_rate / 2
@handsprite.changeBitmap(modeSprites[0]) # point1
else
@handsprite.changeBitmap(modeSprites[1]) # point2
end
end
@handsprite.changeBitmap(getSplicerIcon) if @fusing
@frame += 1
@frame = 0 if @frame >= Graphics.frame_rate
@updating = false
end
def multiHeldPokemon
@multiheldpkmn.delete_if { |pkmn| pkmn.disposed? }
@holding = false if !@heldpkmn && @multiheldpkmn.length == 0
return @multiheldpkmn
end
def holdingSingle?
return self.heldPokemon && @holding
end
def grabMulti(sprites)
@grabbingState = 1
@multiheldpkmn = sprites
for pkmn in @multiheldpkmn
pkmn.viewport = self.viewport
pkmn.z = 1
end
self.z = 2
end
end

View File

@@ -0,0 +1,152 @@
# selection_helpers.rb (can be split into separate file)
module SelectionConstants
BOX_NAME = -1
PARTY = -2
CLOSE = -3
PREV_BOX = -4
NEXT_BOX = -5
end
module SelectionHelper
# Compute visual selection rectangle. Returns a Rect or nil.
def self.compute_rect(scene, screen, box, selected)
return nil unless screen.multiSelectRange
displayRect = Rect.new(0, 0, 1, 1)
if box == SelectionConstants::BOX_NAME
xvalues = []
yvalues = []
for i in 0...Settings::MAX_PARTY_SIZE
xvalues << scene.sprites["boxparty"].x + 18 + 72 * (i % 2)
yvalues << scene.sprites["boxparty"].y + 2 + 16 * (i % 2) + 64 * (i / 2)
end
indexes = screen.getMultiSelection(box, selected)
# defensive: if indexes empty, return nil
return nil if indexes.nil? || indexes.empty?
minx = xvalues[indexes[0]]
miny = yvalues[indexes[0]] + 16
maxx = xvalues[indexes[indexes.length - 1]] + 72 - 8
maxy = yvalues[indexes[indexes.length - 1]] + 64
displayRect.set(minx, miny, maxx - minx, maxy - miny)
else
indexRect = screen.getSelectionRect(box, selected)
return nil if indexRect.nil?
displayRect.x = scene.sprites["box"].x + 10 + (48 * indexRect.x)
displayRect.y = scene.sprites["box"].y + 30 + (48 * indexRect.y) + 16
displayRect.width = indexRect.width * 48 + 16
displayRect.height = indexRect.height * 48
end
displayRect
end
end
module SelectionNavigator
include SelectionConstants
# Navigate general box grid (supports wrap & special indices)
def self.navigate_box(key, selection, screen)
case key
when Input::UP
if screen.multiSelectRange
selection -= PokemonBox::BOX_WIDTH
selection += PokemonBox::BOX_SIZE if selection < 0
elsif selection == BOX_NAME
selection = PARTY
elsif selection == PARTY
selection = PokemonBox::BOX_SIZE - 1 - PokemonBox::BOX_WIDTH * 2 / 3
elsif selection == CLOSE
selection = PokemonBox::BOX_SIZE - PokemonBox::BOX_WIDTH / 3
else
selection -= PokemonBox::BOX_WIDTH
selection = BOX_NAME if selection < 0
end
when Input::DOWN
if screen.multiSelectRange
selection += PokemonBox::BOX_WIDTH
selection -= PokemonBox::BOX_SIZE if selection >= PokemonBox::BOX_SIZE
elsif selection == BOX_NAME
selection = PokemonBox::BOX_WIDTH / 3
elsif selection == PARTY
selection = BOX_NAME
elsif selection == CLOSE
selection = BOX_NAME
else
selection += PokemonBox::BOX_WIDTH
if selection >= PokemonBox::BOX_SIZE
if selection < PokemonBox::BOX_SIZE + PokemonBox::BOX_WIDTH / 2
selection = PARTY
else
selection = CLOSE
end
end
end
when Input::LEFT
if screen.multiSelectRange
if (selection % PokemonBox::BOX_WIDTH) == 0
selection += PokemonBox::BOX_WIDTH - 1
else
selection -= 1
end
elsif selection == BOX_NAME
selection = PREV_BOX
elsif selection == PARTY
selection = CLOSE
elsif selection == CLOSE
selection = PARTY
elsif (selection % PokemonBox::BOX_WIDTH) == 0
selection += PokemonBox::BOX_WIDTH - 1
else
selection -= 1
end
when Input::RIGHT
if screen.multiSelectRange
if (selection % PokemonBox::BOX_WIDTH) == PokemonBox::BOX_WIDTH - 1
selection -= PokemonBox::BOX_WIDTH - 1
else
selection += 1
end
elsif selection == BOX_NAME
selection = NEXT_BOX
elsif selection == PARTY
selection = CLOSE
elsif selection == CLOSE
selection = PARTY
elsif (selection % PokemonBox::BOX_WIDTH) == PokemonBox::BOX_WIDTH - 1
selection -= PokemonBox::BOX_WIDTH - 1
else
selection += 1
end
end
selection
end
# Navigate party (left/right/up/down)
def self.navigate_party(key, selection, screen)
maxIndex = screen.multiSelectRange ? Settings::MAX_PARTY_SIZE - 1 : Settings::MAX_PARTY_SIZE
case key
when Input::LEFT
selection -= 1
selection = maxIndex if selection < 0
when Input::RIGHT
selection += 1
selection = 0 if selection > maxIndex
when Input::UP
if selection == Settings::MAX_PARTY_SIZE
selection = Settings::MAX_PARTY_SIZE - 1
else
selection -= 2
selection = selection % Settings::MAX_PARTY_SIZE if screen.multiSelectRange
selection = maxIndex if selection < 0
end
when Input::DOWN
if selection == Settings::MAX_PARTY_SIZE
selection = 0
else
selection += 2
selection = selection % Settings::MAX_PARTY_SIZE if screen.multiSelectRange
selection = maxIndex if selection > maxIndex
end
end
selection
end
end

View File

@@ -0,0 +1,12 @@
class PokemonBoxIcon < IconSprite
attr_accessor :heldox
attr_accessor :heldoy
alias _pokemonBoxIconInitialize initialize
def initialize(*args)
@heldox = 0
@heldoy = 0
_pokemonBoxIconInitialize(*args)
end
end

View File

@@ -0,0 +1,33 @@
class PokemonBoxPartySprite < SpriteWrapper
def placePokemonMulti(index, sprites)
partyIndex = @pokemonsprites.count { |i| i && i.pokemon && !i.disposed? }
for sprite in sprites
@pokemonsprites[partyIndex] = sprite
partyIndex += 1
end
if sprites.length > 0
@pokemonsprites.compact!
refresh
end
end
def grabPokemonMulti(indexes, arrowIndex, arrow)
grabbedSprites = []
arrowX = arrowIndex % 2
arrowY = (arrowIndex / 2).floor
for index in indexes
sprite = @pokemonsprites[index]
if sprite && sprite.pokemon && !sprite.disposed?
sprite.heldox = (index % 2) - arrowX
sprite.heldoy = (index / 2).floor - arrowY
grabbedSprites.push(sprite)
@pokemonsprites[index] = nil
end
end
if grabbedSprites.length > 0
arrow.grabMulti(grabbedSprites)
@pokemonsprites.compact!
refresh
end
end
end

View File

@@ -0,0 +1,39 @@
class PokemonBoxSprite < SpriteWrapper
def placePokemonMulti(index, sprites)
arrowX = index % PokemonBox::BOX_WIDTH
arrowY = (index / PokemonBox::BOX_WIDTH).floor
for sprite in sprites
spriteIndex = (sprite.heldox + arrowX) + (sprite.heldoy + arrowY) * PokemonBox::BOX_WIDTH
@pokemonsprites[spriteIndex] = sprite
@pokemonsprites[spriteIndex].refresh
end
if sprites.length > 0
refresh
end
end
def grabPokemonMulti(indexes, arrowIndex, arrow)
grabbedSprites = []
arrowX = arrowIndex % PokemonBox::BOX_WIDTH
arrowY = (arrowIndex / PokemonBox::BOX_WIDTH).floor
for index in indexes
sprite = @pokemonsprites[index]
if sprite && sprite.pokemon && !sprite.disposed?
sprite.heldox = (index % PokemonBox::BOX_WIDTH) - arrowX
sprite.heldoy = (index / PokemonBox::BOX_WIDTH).floor - arrowY
grabbedSprites.push(sprite)
@pokemonsprites[index] = nil
end
end
if grabbedSprites.length > 0
arrow.grabMulti(grabbedSprites)
update
end
end
end

View File

@@ -0,0 +1,114 @@
class PokemonStorage
def pbDeleteMulti(box, indexes)
for index in indexes
self[box, index] = nil
end
self.party.compact! if box == -1
end
def pbStoreBatch(pokemon_positions_array, box_index = @currentBox, cursor_x = 0, cursor_y = 0)
return -1 if invalid_input?(pokemon_positions_array, box_index)
box_width = PokemonBox::BOX_WIDTH
box_height = PokemonBox::BOX_HEIGHT
coords = all_coords(box_width, box_height)
assignments, unplaced, intended = initialize_assignments(pokemon_positions_array.length)
spiral = spiral_offsets
# Phase 1: Lock in guaranteed spots
lock_guaranteed_spots(pokemon_positions_array, box_index, cursor_x, cursor_y, box_width, box_height,
assignments, unplaced, intended)
available_coords = filter_available_coords(coords, box_index, box_width, assignments)
return :CANT_PLACE if available_coords.length < unplaced.length
# Phase 2: Assign leftovers using spiral fallback
assign_fallback_positions(unplaced, intended, assignments, available_coords, box_width, box_height, cursor_x, cursor_y, spiral)
return :CANT_PLACE if assignments.compact.uniq.length < assignments.compact.length
# Phase 3: Commit placements
commit_assignments(assignments, pokemon_positions_array, box_index, box_width)
unplaced.empty? ? :PLACED_ALL_FREE : :PLACED_OCCUPIED
end
# -----------------------------
# Helper methods
# -----------------------------
def invalid_input?(arr, box_index)
arr.nil? || arr.empty? || self[box_index].is_a?(StorageTransferBox)
end
def all_coords(box_width, box_height)
(0...box_height).flat_map { |y| (0...box_width).map { |x| [x, y] } }
end
def initialize_assignments(length)
[Array.new(length), [], []] # assignments, unplaced, intended
end
def spiral_offsets
[[0,0],[1,0],[0,1],[-1,0],[0,-1],[1,1],[-1,1],[1,-1],[-1,-1],[2,0],[0,2],[-2,0],[0,-2]]
end
def lock_guaranteed_spots(pokemon_positions_array, box_index, cursor_x, cursor_y, box_width, box_height, assignments, unplaced, intended)
pokemon_positions_array.each_with_index do |pk_data, i|
rel_x, rel_y = pk_data[1], pk_data[2]
target_x, target_y = cursor_x + rel_x, cursor_y + rel_y
intended[i] = [target_x, target_y]
if target_x.between?(0, box_width-1) && target_y.between?(0, box_height-1)
index = target_y * box_width + target_x
assignments[i] = [target_x, target_y] if self[box_index, index].nil?
unplaced << i if assignments[i].nil?
else
unplaced << i
end
end
end
def filter_available_coords(coords, box_index, box_width, assignments)
used_coords = assignments.compact
coords.reject { |cx, cy| !self[box_index, cy * box_width + cx].nil? || used_coords.include?([cx, cy]) }
end
def assign_fallback_positions(unplaced, intended, assignments, available_coords, box_width, box_height, cursor_x, cursor_y, spiral)
unplaced.each_with_index do |i, idx|
tx, ty = intended[i] || [cursor_x, cursor_y]
chosen = spiral.map { |offx, offy| [tx + offx, ty + offy] }
.find { |nx, ny| nx.between?(0, box_width-1) && ny.between?(0, box_height-1) && available_coords.include?([nx, ny]) }
chosen ||= available_coords.min_by { |cx, cy| (cx - tx).abs + (cy - ty).abs }
raise :CANT_PLACE unless chosen
assignments[i] = chosen
available_coords.delete(chosen)
end
end
def commit_assignments(assignments, pokemon_positions_array, box_index, box_width)
assignments.each_with_index do |coords, i|
next unless coords
cx, cy = coords
index = cy * box_width + cx
if self[box_index, index].nil?
self[box_index, index] = pokemon_positions_array[i][0]
else
rollback(assignments, pokemon_positions_array, box_index, box_width)
raise :CANT_PLACE
end
end
end
def rollback(assignments, pokemon_positions_array, box_index, box_width)
assignments.each do |coords|
next unless coords
rx, ry = coords
rindex = ry * box_width + rx
self[box_index, rindex] = nil if pokemon_positions_array.any? { |pk| pk[0] == self[box_index, rindex] }
end
end
end

View File

@@ -0,0 +1,318 @@
class PokemonStorageScene
include SelectionConstants
attr_reader :cursormode
attr_reader :screen
attr_reader :sprites
attr_accessor :selection
alias _storageMultiselect_pbStartBox pbStartBox
def pbStartBox(*args)
_storageMultiselect_pbStartBox(*args)
@cursormode = "default"
# create a single selection rect sprite used when needed
@sprites["selectionrect"] = BitmapSprite.new(Graphics.width, Graphics.height, @arrowviewport)
@sprites["selectionrect"].visible = false
end
def pbChangeSelection(key, selection)
if @choseFromParty
return SelectionNavigator.navigate_party(key, selection, @screen)
else
return SelectionNavigator.navigate_box(key, selection, @screen)
end
end
def pbPartyChangeSelection(key, selection)
SelectionNavigator.navigate_party(key, selection, @screen)
end
def pbSelectPartyInternal(party, depositing)
selection = @selection
pbPartySetArrow(@sprites["arrow"], selection)
pbUpdateOverlay(selection, party)
pbSetMosaic(selection)
lastsel = 1
loop do
Graphics.update
Input.update
key = -1
key = Input::DOWN if Input.repeat?(Input::DOWN)
key = Input::RIGHT if Input.repeat?(Input::RIGHT)
key = Input::LEFT if Input.repeat?(Input::LEFT)
key = Input::UP if Input.repeat?(Input::UP)
if key >= 0
pbPlayCursorSE
newselection = pbPartyChangeSelection(key, selection)
if newselection == -1
return -1 if !depositing
elsif newselection == -2
selection = lastsel
else
selection = newselection
end
pbPartySetArrow(@sprites["arrow"], selection)
lastsel = selection if selection > 0
pbUpdateOverlay(selection, party)
pbSetMosaic(selection)
if @screen.multiSelectRange
pbUpdateSelectionRect(-1, selection)
end
end
self.update
if Input.trigger?(Input::ACTION) && @command == 0 # Organize only
if !@screen.pbHolding?
pbPlayDecisionSE
pbNextCursorMode
end
elsif Input.trigger?(Input::BACK)
@selection = selection
return -1
elsif Input.trigger?(Input::USE)
if selection >= 0 && selection < Settings::MAX_PARTY_SIZE
@selection = selection
return selection
elsif selection == Settings::MAX_PARTY_SIZE # Close Box
@selection = selection
return (depositing) ? -3 : -1
end
end
end
end
def pbSelectBoxInternal(_party)
selection = @selection
pbSetArrow(@sprites["arrow"], selection)
pbUpdateOverlay(selection)
pbSetMosaic(selection)
loop do
Graphics.update
Input.update
key = -1
key = Input::DOWN if Input.repeat?(Input::DOWN)
key = Input::RIGHT if Input.repeat?(Input::RIGHT)
key = Input::LEFT if Input.repeat?(Input::LEFT)
key = Input::UP if Input.repeat?(Input::UP)
if key >= 0
pbPlayCursorSE
selection = pbChangeSelection(key, selection)
pbSetArrow(@sprites["arrow"], selection)
if selection == SelectionConstants::PREV_BOX
nextbox = (@storage.currentBox + @storage.maxBoxes - 1) % @storage.maxBoxes
pbSwitchBoxToLeft(nextbox)
@storage.currentBox = nextbox
elsif selection == SelectionConstants::NEXT_BOX
nextbox = (@storage.currentBox + 1) % @storage.maxBoxes
pbSwitchBoxToRight(nextbox)
@storage.currentBox = nextbox
end
selection = BOX_NAME if selection == SelectionConstants::PREV_BOX || selection == SelectionConstants::NEXT_BOX
pbUpdateOverlay(selection)
pbSetMosaic(selection)
if @screen.multiSelectRange
pbUpdateSelectionRect(@storage.currentBox, selection)
end
end
self.update
if Input.trigger?(Input::JUMPUP)
pbPlayCursorSE
nextbox = (@storage.currentBox + @storage.maxBoxes - 1) % @storage.maxBoxes
pbSwitchBoxToLeft(nextbox)
@storage.currentBox = nextbox
pbUpdateOverlay(selection)
pbSetMosaic(selection)
elsif Input.trigger?(Input::JUMPDOWN)
pbPlayCursorSE
nextbox = (@storage.currentBox + 1) % @storage.maxBoxes
pbSwitchBoxToRight(nextbox)
@storage.currentBox = nextbox
pbUpdateOverlay(selection)
pbSetMosaic(selection)
elsif Input.trigger?(Input::SPECIAL) # Jump to box name
if selection != BOX_NAME
pbPlayCursorSE
selection = BOX_NAME
pbSetArrow(@sprites["arrow"], selection)
pbUpdateOverlay(selection)
pbSetMosaic(selection)
end
elsif Input.trigger?(Input::ACTION) && @command == 0 # Organize only
pbPlayDecisionSE
pbNextCursorMode
elsif Input.trigger?(Input::BACK)
@selection = selection
return nil
elsif Input.trigger?(Input::USE)
@selection = selection
if selection >= 0
return [@storage.currentBox, selection]
elsif selection == BOX_NAME
return [SelectionConstants::PREV_BOX, BOX_NAME]
elsif selection == PARTY
return [SelectionConstants::PARTY, BOX_NAME]
elsif selection == CLOSE
return [SelectionConstants::CLOSE, BOX_NAME]
end
end
end
end
def restartBox(screen, command, animate = true)
pbCloseBox(animate)
cursormode = @cursormode
selection = @selection
pbStartBox(screen, command, animate)
pbSetCursorMode(cursormode)
@selection = selection
end
def pbNextCursorMode()
return if @screen.pbHolding?
case @cursormode
when "default"
pbSetCursorMode("multiselect")
when "quickswap" #Disabled
pbSetCursorMode("default")
when "multiselect"
#pbSetCursorMode("quickswap") if !@screen.pbHolding?
pbSetCursorMode("default")
end
end
def pbSetCursorMode(value)
@cursormode = value
@sprites["arrow"].cursormode = value
if @screen.multiSelectRange
@screen.multiSelectRange = nil
pbUpdateSelectionRect(@choseFromParty ? -1 : @storage.currentBox, 0)
end
end
def pbSetHeldPokemon(pokemon)
pokesprite = PokemonBoxIcon.new(pokemon, @arrowviewport)
@sprites["arrow"].grabImmediate(pokesprite)
end
# Scene draws the rect by asking SelectionHelper for the rect
# in PokemonStorageScene
def pbUpdateSelectionRect(box, selected)
rect = SelectionHelper.compute_rect(self, @screen, box, selected)
if rect.nil?
@sprites["selectionrect"].visible = false
return
end
bmp = @sprites["selectionrect"].bitmap
bmp.clear
color = Color.new(0, 255, 0, 50) # semi-transparent green
radius = 12 # corner roundness in pixels
draw_rounded_rect(bmp, rect.x, rect.y, rect.width, rect.height, radius, color)
@sprites["selectionrect"].visible = true
end
# helper method (add to PokemonStorageScene)
def draw_rounded_rect(bmp, x, y, w, h, r, color)
# central rectangle
bmp.fill_rect(x + r, y, w - 2 * r, h, color)
bmp.fill_rect(x, y + r, w, h - 2 * r, color)
# corner circles
(0..r).each do |dy|
dx = (r * r - dy * dy) ** 0.5
# top-left
bmp.fill_rect(x + r - dx, y + r - dy, 2 * dx, 1, color)
# top-right
bmp.fill_rect(x + w - r - dx, y + r - dy, 2 * dx, 1, color)
# bottom-left
bmp.fill_rect(x + r - dx, y + h - r + dy - 1, 2 * dx, 1, color)
# bottom-right
bmp.fill_rect(x + w - r - dx, y + h - r + dy - 1, 2 * dx, 1, color)
end
end
# --- Animation methods (Scene-only) ---
# The scene only animates; it does not change game state or run rules.
def animate_hold_multi(box, selected, selected_index)
pbSEPlay("GUI storage pick up")
if box == BOX_NAME
@sprites["boxparty"].grabPokemonMulti(selected, selected_index, @sprites["arrow"])
else
@sprites["box"].grabPokemonMulti(selected, selected_index, @sprites["arrow"])
end
while @sprites["arrow"].grabbing?
Graphics.update
Input.update
self.update
end
end
def animate_place_multi(box, index)
pbSEPlay("GUI storage put down")
heldpokesprites = @sprites["arrow"].multiHeldPokemon
@sprites["arrow"].place
while @sprites["arrow"].placing?
Graphics.update
Input.update
self.update
end
if box == BOX_NAME
@sprites["boxparty"].placePokemonMulti(index, heldpokesprites)
else
@sprites["box"].placePokemonMulti(index, heldpokesprites)
end
@boxForMosaic = @storage.currentBox
@selectionForMosaic = index
end
def animate_release_multi(box, selected)
release_sprites = []
for index in selected
sprite = nil
if box == BOX_NAME
sprite = @sprites["boxparty"].getPokemon(index)
else
sprite = @sprites["box"].getPokemon(index)
end
release_sprites << sprite if sprite
end
if release_sprites.length > 0
for sprite in release_sprites
sprite.release
end
while release_sprites[0].releasing?
Graphics.update
for sprite in release_sprites
sprite.update
end
self.update
end
end
end
def pbHardRefresh
oldPartyY = @sprites["boxparty"].y
@sprites["box"].dispose
@sprites["box"] = PokemonBoxSprite.new(@storage, @storage.currentBox, @boxviewport)
@sprites["boxparty"].dispose
@sprites["boxparty"] = PokemonBoxPartySprite.new(@storage.party, @boxsidesviewport)
@sprites["boxparty"].y = oldPartyY
end
def pbCloseBox(animate = true)
pbFadeOutAndHide(@sprites) if animate
pbDisposeSpriteHash(@sprites)
@markingbitmap.dispose if @markingbitmap
@boxviewport.dispose
@boxsidesviewport.dispose
@arrowviewport.dispose
end
# Convenience wrapper for external code that expects pbPlaceMulti/pbHoldMulti naming:
alias pbPlaceMulti animate_place_multi
alias pbHoldMulti animate_hold_multi
alias pbReleaseMulti animate_release_multi
end

View File

@@ -0,0 +1,415 @@
class PokemonStorageScreen
include SelectionConstants
attr_accessor :multiheldpkmn
attr_accessor :multiSelectRange
alias _storageMultiSelect_initialize initialize
def initialize(*args)
_storageMultiSelect_initialize(*args)
@multiheldpkmn = []
@multiSelectRange = nil
end
# Top-level loop: delegates move and release actions to screen-level methods.
def pcOrganizeCommand()
isTransferBox = @storage[@storage.currentBox].is_a?(StorageTransferBox)
loop do
selected = @scene.pbSelectBox(@storage.party)
if selected == nil
if pbHolding?
if @fusionMode
cancelFusion
else
pbDisplay(_INTL("You're holding a Pokémon!"))
end
next
end
if @multiSelectRange
pbPlayCancelSE
@multiSelectRange = nil
@scene.pbUpdateSelectionRect(0, 0)
next
end
next if pbConfirm(_INTL("Continue Box operations?"))
break
elsif selected[0] == SelectionConstants::CLOSE
if pbHolding?
if @multiSelectRange
pbPlayCancelSE
@multiSelectRange = nil
@scene.pbUpdateSelectionRect(0, 0)
next
else
pbDisplay(_INTL("You're holding a Pokémon!"))
next
end
end
if pbConfirm(_INTL("Exit from the Box?"))
pbSEPlay("PC close")
break
end
next
elsif selected[0] == SelectionConstants::PREV_BOX
pbBoxCommands
else
pokemon = @storage[selected[0], selected[1]]
heldpoke = pbHeldPokemon
next if !heldpoke && !pokemon && @scene.cursormode != "multiselect"
if @scene.cursormode == "multiselect"
multiSelectAction(selected)
elsif @scene.cursormode == "quickswap"
quickSwap(selected, pokemon)
elsif @fusionMode
pbFusionCommands(selected)
else
echoln "pcOrganizeCommand?"
organizeActions(selected, pokemon, heldpoke, isTransferBox)
end
end
end
@scene.pbCloseBox
end
def selectAllBox
@scene.pbSetCursorMode("multiselect")
selected_index = PokemonBox::BOX_SIZE - 1
@multiSelectRange = [0, nil]
box = @storage.currentBox
@scene.pbUpdateSelectionRect(box, selected_index,)
@scene.selection = selected_index
end
def pbBoxCommands
is_holding_pokemon = pbHolding?
if @scene.cursormode == "multiselect"
if is_holding_pokemon
return dropAllHeldPokemon
else
return selectAllBox
end
end
cmd_jump = _INTL("Jump")
cmd_select = _INTL("Select all")
cmd_wallpaper = _INTL("Wallpaper")
cmd_name = _INTL("Name")
cmd_info = _INTL("Info")
cmd_cancel = _INTL("Cancel")
commands = []
commands << cmd_jump
commands << cmd_select unless is_holding_pokemon
commands << cmd_wallpaper
commands << cmd_name if !@storage[@storage.currentBox].is_a?(StorageTransferBox)
commands << cmd_info if @storage[@storage.currentBox].is_a?(StorageTransferBox)
commands << cmd_cancel
command = pbShowCommands(
_INTL("What do you want to do?"), commands)
case commands[command]
when cmd_jump
boxCommandJump
when cmd_wallpaper
boxCommandSetWallpaper
when cmd_name
boxCommandName
when cmd_info
boxCommandTransferInfo
when cmd_select
selectAllBox
end
end
def singlePokemonCommands(selected)
#@multiSelectRange = nil
#@scene.pbUpdateSelectionRect(selected[0], selected[1])
isTransferBox = @storage[@storage.currentBox].is_a?(StorageTransferBox)
pokemon = @storage[selected[0], selected[1]]
heldpoke = pbHeldPokemon
return organizeActions(selected, pokemon, heldpoke, isTransferBox)
end
def multipleSelectedPokemonCommands(selected, pokemonCount)
commands = []
cmdMove = -1
cmdRelease = -1
cmdCancel = -1
cmdSort = -1
helptext = _INTL("Selected {1} Pokémon.", pokemonCount)
commands[cmdMove = commands.length] = _INTL("Move")
commands[cmdSort = commands.length] = _INTL("Sort")
commands[cmdRelease = commands.length] = _INTL("Release") if $DEBUG
commands[cmdCancel = commands.length] = _INTL("Cancel")
command = pbShowCommands(helptext, commands)
if command == cmdMove
pbHoldMulti(selected[0], selected[1])
elsif command == cmdSort
pbSortMulti(selected[0])
elsif command == cmdRelease
pbReleaseMulti(selected[0])
end
end
def dropAllHeldPokemon
multiSelectAction([@storage.currentBox, 0])
end
# Multi-select flow: validates and delegates animations to scene.
def multiSelectAction(selected)
return unless @scene.cursormode == "multiselect"
if pbMultiHeldPokemon.length > 0
# placing multi-held from screen's held list
place_result = pbPlaceMulti(selected[0], selected[1])
if place_result == :PLACED_OCCUPIED
pbSEPlay("GUI party switch")
end
elsif !@multiSelectRange
pbPlayDecisionSE
@multiSelectRange = [selected[1], nil]
@scene.pbUpdateSelectionRect(selected[0], selected[1])
return
elsif !@multiSelectRange[1]
@multiSelectRange[1] = selected[1]
pokemonCount = 0
noneggCount = 0
for index in getMultiSelection(selected[0], nil)
pokemonCount += 1 if @storage[selected[0], index]
if @storage[selected[0], index]
noneggCount += 1 unless @storage[selected[0], index].egg?
end
end
if pokemonCount == 0
pbPlayCancelSE
@multiSelectRange = nil
@scene.pbUpdateSelectionRect(selected[0], selected[1])
return
elsif pokemonCount == 1 && @storage[selected[0], selected[1]]
singlePokemonCommands(selected)
else
multipleSelectedPokemonCommands(selected, pokemonCount)
end
@multiSelectRange = nil
@scene.pbUpdateSelectionRect(selected[0], selected[1])
end
end
# --- Screen-side game-rule methods (validate, commit, then animate) ---
# Validate & pick up a selection of multiple Pokémon (logical)
def pbHoldMulti(box, selected_index)
selected = getMultiSelection(box, nil)
return if selected.length == 0
selected_pos = getBoxPosition(box, selected_index)
able_count = 0
new_held = []
final_selected = []
for index in selected
pokemon = @storage[box, index]
next if !pokemon
able_count += 1 if pbAble?(pokemon)
pos = getBoxPosition(box, index)
new_held << [pokemon, pos[0] - selected_pos[0], pos[1] - selected_pos[1]]
final_selected << index
end
# Prevent taking last pokemon out of party
if box == BOX_NAME && pbAbleCount == able_count
if new_held.length > 1
# deselect first able pokemon for convenience
for i in 0...new_held.length
if pbAble?(new_held[i][0])
new_held.delete_at(i)
final_selected.delete_at(i)
break
end
end
else
pbPlayBuzzerSE
pbDisplay(_INTL("That's your last Pokémon!"))
return
end
end
# Clear selection, animate pickup, update logical state
@multiSelectRange = nil
@scene.pbUpdateSelectionRect(0, 0)
@scene.animate_hold_multi(box, final_selected, selected_index) # animation
@multiheldpkmn = new_held
@storage.pbDeleteMulti(box, final_selected)
@scene.pbRefresh
end
# Check if all held pokémon can strictly fit at the target positions (no overlap, no OOB).
# Commit them to storage and animate the placement.
def pbPlaceMulti(box, selected_index)
return if @multiheldpkmn.nil? || @multiheldpkmn.empty?
selected_pos = getBoxPosition(box, selected_index)
if box >= 0
# Validate every target slot is in-bounds and unoccupied
need_fill = false
for held in @multiheldpkmn
held_x = held[1] + selected_pos[0]
held_y = held[2] + selected_pos[1]
if out_of_bounds?(box, held_x, held_y) || occupied?(box, held_x, held_y)
need_fill = true
end
end
if need_fill
store_result = @storage.pbStoreBatch(@multiheldpkmn, box, selected_pos[0], selected_pos[1])
if store_result == :CANT_PLACE || !store_result
pbDisplay(_INTL("There's not enough room!"))
return
end
@scene.animate_place_multi(box, selected_index)
@multiheldpkmn = []
@scene.pbHardRefresh
@scene.restartBox(self, @command, false)
@storage = $PokemonStorage
@scene.pbRefresh
@multiheldpkmn = []
@boxForMosaic = @storage.currentBox
@selectionForMosaic = selected_index
return store_result
end
# All validated: animate then commit
@scene.animate_place_multi(box, selected_index)
for held in @multiheldpkmn
pokemon = held[0]
held_x = held[1] + selected_pos[0]
held_y = held[2] + selected_pos[1]
idx = held_x + held_y * PokemonBox::BOX_WIDTH
@storage[box, idx] = pokemon
end
else
# Party placement: validate space
party_count = @storage.party.length
if party_count + @multiheldpkmn.length > Settings::MAX_PARTY_SIZE
pbDisplay(_INTL("There's not enough room!"))
return
end
@scene.animate_place_multi(box, selected_index)
for held in @multiheldpkmn
pokemon = held[0]
@storage.party.push(pokemon)
end
end
@scene.pbRefresh
@multiheldpkmn = []
end
# Validate release rules, animate release, then delete from storage
def pbReleaseMulti(box)
selected = getMultiSelection(box, nil)
return if selected.length == 0
able_count = 0
final_released = []
for index in selected
pokemon = @storage[box, index]
next if !pokemon
if pokemon.owner.name == "RENTAL"
pbDisplay(_INTL("This Pokémon cannot be released"))
return
elsif pokemon.egg?
pbDisplay(_INTL("You can't release an Egg."))
return false
elsif pokemon.mail
pbDisplay(_INTL("Please remove the mail."))
return false
end
able_count += 1 if pbAble?(pokemon)
final_released << index
end
if box == BOX_NAME && pbAbleCount == able_count
pbPlayBuzzerSE
pbDisplay(_INTL("That's your last Pokémon!"))
return
end
command = pbShowCommands(_INTL("Release {1} Pokémon?", final_released.length), [_INTL("No"), _INTL("Yes")])
if command == 1
command_confirm = pbShowCommands(_INTL("The {1} Pokémon will be lost forever. Release them?", final_released.length), [_INTL("No"), _INTL("Yes")])
if command_confirm == 1
@multiSelectRange = nil
@scene.pbUpdateSelectionRect(0, 0)
@scene.animate_release_multi(box, final_released)
@storage.pbDeleteMulti(box, final_released)
@scene.pbRefresh
pbDisplay(_INTL("The Pokémon were released."))
pbDisplay(_INTL("Bye-bye!"))
@scene.pbRefresh
end
end
return
end
def out_of_bounds?(box, x, y)
width = (box == BOX_NAME ? 2 : PokemonBox::BOX_WIDTH)
height = (box == BOX_NAME ? (Settings::MAX_PARTY_SIZE / 2.0).ceil : PokemonBox::BOX_HEIGHT)
x < 0 || y < 0 || x >= width || y >= height
end
def occupied?(box, x, y)
idx = x + y * PokemonBox::BOX_WIDTH
!!@storage[box, idx]
end
def pbMultiHeldPokemon
@multiheldpkmn
end
def pbHolding?
return @heldpkmn != nil || (@multiheldpkmn && @multiheldpkmn.length > 0)
end
def getSelectionRect(box, currentSelected)
range_end = (currentSelected != nil ? currentSelected : @multiSelectRange && @multiSelectRange[1])
return nil unless @multiSelectRange && @multiSelectRange[0] && range_end
box_width = box == BOX_NAME ? 2 : PokemonBox::BOX_WIDTH
ax = @multiSelectRange[0] % box_width
ay = (@multiSelectRange[0].to_f / box_width).floor
bx = range_end % box_width
by = (range_end.to_f / box_width).floor
minx = [ax, bx].min
miny = [ay, by].min
maxx = [ax, bx].max
maxy = [ay, by].max
Rect.new(minx, miny, maxx - minx + 1, maxy - miny + 1)
end
def getMultiSelection(box, currentSelected)
rect = getSelectionRect(box, currentSelected)
return [] if rect.nil?
ret = []
for j in (rect.y)..(rect.y + rect.height - 1)
for i in (rect.x)..(rect.x + rect.width - 1)
ret << getBoxIndex(box, i, j)
end
end
ret
end
def getBoxIndex(box, x, y)
box_width = box == BOX_NAME ? 2 : PokemonBox::BOX_WIDTH
x + y * box_width
end
def getBoxPosition(box, index)
box_width = box == BOX_NAME ? 2 : PokemonBox::BOX_WIDTH
[index % box_width, (index.to_f / box_width).floor]
end
end

View File

@@ -0,0 +1,151 @@
class PokemonStorageScreen
# --- Define sortable criteria ---
def sortable_criteria
[
{
key: :dex,
label: _INTL("By Pokédex number"),
value_proc: ->(p) { p.id_number || 0 },
friendly: [_INTL("Lowest to Highest Pokédex #"), _INTL("Highest to Lowest Pokédex #")]
},
{
key: :head_dex,
label: _INTL("By head Pokédex number"),
value_proc: ->(p) { p.head_id || 0 },
friendly: [_INTL("Lowest to Highest Pokédex #"), _INTL("Highest to Lowest Pokédex #")]
},
{
key: :body_dex,
label: _INTL("By body Pokédex number"),
value_proc: ->(p) { p.body_id || 0 },
friendly: [_INTL("Lowest to Highest Pokédex #"), _INTL("Highest to Lowest Pokédex #")]
},
{
key: :alpha_species,
label: _INTL("By species name"),
value_proc: ->(p) { (p.species.to_s || "").downcase },
friendly: [_INTL("A to Z"), _INTL("Z to A")]
},
{
key: :alpha,
label: _INTL("By nickname"),
value_proc: ->(p) { (p.name || "").downcase },
friendly: [_INTL("A to Z"), _INTL("Z to A")]
},
{
key: :level,
label: _INTL("By level"),
value_proc: ->(p) { p.level || 0 },
friendly: [_INTL("Lowest to Highest level"), _INTL("Highest to Lowest level")]
},
{
key: :type,
label: _INTL("By type"),
value_proc: ->(p) {
# Grab both types (fallbacks in case one is nil)
t1 = p.type1 || :NORMAL
t2 = p.type2 || ""
[
GameData::Type.get(t1).name,
GameData::Type.get(t2).name
]
},
friendly: [_INTL("A to Z by type"), _INTL("Z to A by type")]
},
{
key: :date,
label: _INTL("By date caught"),
value_proc: ->(p) { p.timeReceived || Time.at(0) },
friendly: [_INTL("Oldest to Newest"), _INTL("Newest to Oldest")]
},
{
key: :invert,
label: _INTL("Reverse"),
value_proc: ->(p) { reverse },
friendly: [_INTL("Reverse the order")]
},
{
key: :random,
label: _INTL("Shuffle"),
value_proc: ->(p) { rand },
friendly: [_INTL("Randomize the order")]
},
]
end
# --- Ask which criterion to sort by ---
def pbAskSortCriterion
commands = sortable_criteria.map { |c| c[:label] } + [_INTL("Cancel")]
cmd = pbShowCommands(_INTL("Sort selected Pokémon how?"), commands)
return nil if cmd == commands.length - 1
return nil if cmd <= -1
return cmd
end
# --- Ask for order using friendly text ---
def pbAskSortOrder(criterion_index)
crit = sortable_criteria[criterion_index]
orders = crit[:friendly] + [_INTL("Cancel")]
ord = pbShowCommands(_INTL("Sort order?"), orders)
return nil if ord <= -1
return nil if ord == orders.length - 1
return ord == 1 # true if descending
end
# --- Sort the array according to criterion and order ---
def sort_pokemon_array!(arr, criterion_index, descending)
crit = sortable_criteria[criterion_index]
if crit[:key] == :invert
arr.reverse!
return
end
arr.sort_by! { |p| crit[:value_proc].call(p) }
arr.reverse! if descending
end
# --- Main method stays mostly unchanged ---
def pbSortMulti(box)
selected = getMultiSelection(box, nil)
return if selected.empty?
pokes = selected.map { |idx| @storage[box, idx] }.compact
return if pokes.empty?
criterion = pbAskSortCriterion
return if criterion.nil?
descending = pbAskSortOrder(criterion)
return if descending.nil?
sort_pokemon_array!(pokes, criterion, descending)
# Clear selected slots
selected.each { |idx| @storage[box, idx] = nil }
# Refill the rectangle row-by-row
rect = getSelectionRect(box, nil)
i = 0
if rect
for y in rect.y...(rect.y + rect.height)
for x in rect.x...(rect.x + rect.width)
break if i >= pokes.length
idx = getBoxIndex(box, x, y)
@storage[box, idx] = pokes[i]
i += 1
end
end
else
selected.each do |idx|
break if i >= pokes.length
@storage[box, idx] = pokes[i]
i += 1
end
end
pbSEPlay("GUI party switch")
@scene.pbHardRefresh
end
end