Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- # ===============================================================================
- # CRAFTING UI - SIMPLIFIED AND CONFIGURABLE VERSION (FIXED)
- # File: Plugins/CraftingSystem/CraftingUI.rb
- # ===============================================================================
- #===============================================================================
- # CUSTOMIZATION CONSTANTS - MODIFY THESE TO CHANGE UI LAYOUT
- #===============================================================================
- module CraftingUIConfig
- # === RECIPE LIST WINDOW CONFIGURATION ===
- RECIPE_WINDOW_X = 228 # X position of recipe list window
- RECIPE_WINDOW_Y = 32 # Y position of recipe list window
- RECIPE_WINDOW_WIDTH = 250 # Width of recipe list window
- RECIPE_WINDOW_HEIGHT = 200 # Height of recipe list window
- RECIPE_WINDOW_PADDING = 0 # Internal padding for recipe window
- RECIPE_ROW_HEIGHT = 32 # Height of each recipe row
- RECIPES_PER_PAGE = 6 # Number of recipes visible at once
- RECIPE_WINDOW_Y_OFFSET = 16 # Y offset from background graphic to actual window
- RECIPE_WINDOW_HEIGHT_OFFSET = 24 # Height reduction from background graphic
- # === RECIPE LIST CONFIGURATION ===
- RECIPE_LIST_Y_OFFSET = 0 # FIXED: Remove negative offset that causes clipping
- # === RECIPE TEXT AREA MARGINS ===
- TEXT_AREA_LEFT_MARGIN = 0 # Extra space on left side for text positioning
- TEXT_AREA_RIGHT_MARGIN = 0 # Extra space on right side
- TEXT_AREA_TOP_MARGIN = 0 # FIXED: Remove negative offset that causes clipping
- TEXT_AREA_BOTTOM_MARGIN = 0 # Extra space on bottom for text positioning
- # === RECIPE TEXT POSITIONING ===
- RECIPE_TEXT_X_OFFSET = 8 # Position relative to the margin
- RECIPE_TEXT_Y_OFFSET = 8 # FIXED: Add positive offset to move text down from top edge
- RECIPE_TEXT_MAX_CHAR_CALC = 40 # Used in max_chars calculation (rect.width - this) / 8
- QUANTITY_TEXT_RIGHT_MARGIN = 8 # Right margin for quantity text (x2, x3, etc.)
- # === CURSOR CONFIGURATION ===
- CURSOR_X_OFFSET = 0 # How far left/right to move cursor from text area
- CURSOR_Y_OFFSET = 0 # How far up/down to move cursor
- # === INGREDIENTS DISPLAY CONFIGURATION ===
- INGREDIENTS_WINDOW_X = 22 # X position of ingredients window
- INGREDIENTS_WINDOW_Y = 34 # Y position of ingredients window
- INGREDIENTS_WINDOW_PADDING = 8 # Internal padding for ingredients window only
- INGREDIENTS_TITLE_Y_OFFSET = 16 # Y offset for "Ingredients" title from window top
- INGREDIENTS_LIST_Y_OFFSET = 42 # Y offset for ingredient list from window top
- INGREDIENT_SPACING = 28 # Vertical spacing between ingredients
- INGREDIENT_ICON_SIZE = 24 # Size of ingredient icons (24x24)
- INGREDIENT_TEXT_X_OFFSET = 28 # X offset for ingredient text from icon
- INGREDIENT_TEXT_Y_OFFSET = 4 # Y offset for ingredient text from icon top
- INGREDIENT_FONT_SIZE_REDUCTION = 2 # How much smaller ingredient font should be
- # === RESULT ITEM DISPLAY CONFIGURATION ===
- RESULT_ITEM_PANEL_X = 24 # X position of result item icon
- RESULT_ITEM_PANEL_Y = 313 # Y position of result item icon
- RESULT_ITEM_ICON_SIZE = 48 # Size of result item icon
- RESULT_TEXT_X_OFFSET = 80 # X offset for result text from icon
- RESULT_TEXT_Y = 300 # Y position for result item name
- RESULT_DESC_Y_OFFSET = 28 # Y offset for description from name
- RESULT_DESC_LINE_HEIGHT = 25 # Line height for description text
- RESULT_DESC_MAX_CHARS = 35 # Max characters per description line
- RESULT_DESC_WRAP_THRESHOLD = 40 # Description length before word wrapping
- # === CATEGORY WINDOW CONFIGURATION ===
- CATEGORY_WINDOW_Y = 220 # Y position of category window
- CATEGORY_TEXT_Y_OFFSET = -8 # Y offset for category text from center
- ARROW_LEFT_X = 8 # X position of left arrow
- ARROW_RIGHT_X = 188 # X position of right arrow
- ARROW_UP_X = 466 # X position of up arrow
- ARROW_UP_Y = 20 # Y position of up arrow
- ARROW_OPACITY_DIM = 128 # Opacity for dimmed arrows
- ARROW_OPACITY_BRIGHT = 255 # Opacity for active arrows
- # === WINDOW BACKGROUND POSITIONS ===
- DESC_WINDOW_Y = 280 # Y position of description background window
- # === FONT CONFIGURATION ===
- BASE_FONT_SIZE = 0 # Offset from system font size (0 = use system default)
- INGREDIENT_MIN_FONT_SIZE = 16 # Minimum font size for ingredients
- # === COLOR CONFIGURATION ===
- COLOR_CRAFTABLE = Color.new(0, 120, 0) # Green - can craft
- COLOR_LOCKED = Color.new(80, 80, 80) # Gray - unlocked but missing materials
- COLOR_UNAVAILABLE = Color.new(150, 150, 150) # Light gray - locked
- COLOR_AVAILABLE = Color.new(0, 150, 0) # Green for available ingredients
- COLOR_MISSING = Color.new(150, 50, 50) # Red for missing ingredients
- COLOR_TEXT = Color.new(248, 248, 248) # White text
- COLOR_SHADOW = Color.new(0, 0, 0) # Black shadow/outline
- COLOR_CANCEL = Color.new(60, 60, 60) # Cancel option color
- COLOR_TITLE = Color.new(0, 0, 0) # Title text color (ingredients)
- # === GRAPHICS PATHS - CHANGE THESE TO USE DIFFERENT GRAPHICS ===
- CURSOR_GRAPHIC = "Graphics/UI/Crafting/cursor"
- BACKGROUND_GRAPHIC = "Graphics/UI/Crafting/background.png"
- INGREDIENTS_BG_GRAPHIC = "Graphics/UI/Crafting/ingredients_window.png"
- RECIPE_BG_GRAPHIC = "Graphics/UI/Crafting/recipe_window.png"
- CATEGORY_BG_GRAPHIC = "Graphics/UI/Crafting/category_window.png"
- DESC_BG_GRAPHIC = "Graphics/UI/Crafting/craftable_description.png"
- ARROW_LEFT_GRAPHIC = "Graphics/UI/Crafting/arrow_left.png"
- ARROW_RIGHT_GRAPHIC = "Graphics/UI/Crafting/arrow_right.png"
- ARROW_UP_GRAPHIC = "Graphics/UI/Crafting/arrow_vertical.png"
- ITEM_GRAPHICS_PATH = "Graphics/Items/" # Path to item graphics folder
- # === PERFORMANCE CONFIGURATION ===
- REFRESH_ON_INDEX_CHANGE = true # Whether to refresh ingredients when selection changes
- end
- #===============================================================================
- # Recipe List Window (Using Built-in Cursor Like Pokemon Essentials Bag)
- #===============================================================================
- class Window_CraftingRecipeList < Window_DrawableCommand
- include CraftingUIConfig
- attr_reader :current_category
- attr_accessor :recipes
- def initialize(x, y, width, height)
- @current_category = "All Items"
- @recipes = []
- @navigating = false # Flag to prevent refresh interference
- super(x, y, width, height)
- @selarrow = AnimatedBitmap.new(CURSOR_GRAPHIC)
- self.windowskin = nil
- @row_height = RECIPE_ROW_HEIGHT
- # Initialize index explicitly
- @index = 0
- # CRITICAL: Disable built-in navigation completely
- self.active = false
- # Create cursor as separate sprite
- @cursor_sprite = nil
- update_recipe_list
- end
- def dispose
- @cursor_sprite&.dispose
- @selarrow&.dispose
- super
- end
- # FIXED: Simplified setup_contents method
- def setup_contents
- dwidth = self.width - self.borderX
- dheight = self.height - self.borderY
- self.contents = pbDoEnsureBitmap(self.contents, dwidth, dheight)
- self.contents.clear
- setup_font(self.contents)
- # Removed all the complex offset calculations that were causing issues
- end
- # === CORE WINDOW METHODS ===
- def item
- return nil if @recipes.empty? && self.index != 0
- return :cancel if self.index == @recipes.length
- return :cancel if @recipes.empty? && self.index == 0
- return nil if self.index >= @recipes.length
- return @recipes[self.index]
- end
- def itemCount
- @recipes.empty? ? 1 : @recipes.length + 1
- end
- def page_row_max
- RECIPES_PER_PAGE
- end
- def page_item_max
- RECIPES_PER_PAGE
- end
- # FIXED: Simplified itemRect method
- def itemRect(item)
- return Rect.new(0, 0, 0, 0) if item < 0 || item >= @item_max
- return Rect.new(0, 0, 0, 0) if item < self.top_item || item >= self.top_item + self.page_item_max
- cursor_width = (self.width - self.borderX - ((@column_max - 1) * @column_spacing)) / @column_max
- x = (item % @column_max * (cursor_width + @column_spacing)) + TEXT_AREA_LEFT_MARGIN
- y = (item - self.top_item) * @row_height + TEXT_AREA_TOP_MARGIN # Simplified Y calculation
- # Apply all margins to the rect dimensions
- width = cursor_width - TEXT_AREA_LEFT_MARGIN - TEXT_AREA_RIGHT_MARGIN
- height = @row_height - TEXT_AREA_TOP_MARGIN - TEXT_AREA_BOTTOM_MARGIN
- return Rect.new(x, y, width, height)
- end
- # === DRAWING METHODS ===
- def drawCursor(index, rect)
- # Empty - cursor is handled by separate sprite
- end
- def drawItem(index, _count, rect)
- # Handle empty recipe list or cancel option
- if @recipes.empty? || index == @recipes.length
- draw_cancel_option(rect)
- return
- end
- return if index >= @recipes.length
- recipe = @recipes[index]
- return unless recipe
- draw_recipe_item(recipe, rect)
- end
- # === PRIVATE DRAWING HELPERS ===
- private
- def draw_cancel_option(rect)
- textpos = [["Cancel", rect.x + RECIPE_TEXT_X_OFFSET, rect.y + RECIPE_TEXT_Y_OFFSET, :left, COLOR_CANCEL, COLOR_TEXT]]
- pbDrawTextPositions(self.contents, textpos)
- end
- def draw_recipe_item(recipe, rect)
- color = get_recipe_color(recipe)
- max_chars = ((rect.width - RECIPE_TEXT_MAX_CHAR_CALC) / 8).to_i
- name = recipe.name.length > max_chars ? recipe.name[0..max_chars-3] + "..." : recipe.name
- textpos = [[name, rect.x + RECIPE_TEXT_X_OFFSET, rect.y + RECIPE_TEXT_Y_OFFSET, :left, color, COLOR_TEXT]]
- if recipe.result_quantity > 1
- qty_text = "x#{recipe.result_quantity}"
- qty_x = rect.x + rect.width - self.contents.text_size(qty_text).width - QUANTITY_TEXT_RIGHT_MARGIN
- textpos.push([qty_text, qty_x, rect.y + RECIPE_TEXT_Y_OFFSET, :left, color, COLOR_TEXT])
- end
- pbDrawTextPositions(self.contents, textpos)
- end
- def get_recipe_color(recipe)
- if recipe.can_craft?
- COLOR_CRAFTABLE
- elsif recipe.unlocked?
- COLOR_LOCKED
- else
- COLOR_UNAVAILABLE
- end
- end
- # === PUBLIC METHODS ===
- public
- def refresh
- @item_max = itemCount
- self.update_cursor_rect
- setup_contents
- # Draw items and update cursor sprite
- draw_all_visible_items
- update_cursor_sprite # Update separate cursor sprite
- end
- # FIXED: Make cursor position exactly match text position
- def update_cursor_sprite
- @cursor_sprite&.dispose
- return if @index < 0
- # Get the item rectangle
- rect = itemRect(@index)
- # Position cursor exactly where the text is drawn
- cursor_x = self.x + self.borderX + rect.x + RECIPE_TEXT_X_OFFSET
- cursor_y = self.y + self.borderY + rect.y + RECIPE_TEXT_Y_OFFSET
- cursor_width = rect.width - RECIPE_TEXT_X_OFFSET
- cursor_height = rect.height - RECIPE_TEXT_Y_OFFSET
- # Create cursor sprite that matches the text position exactly
- @cursor_sprite = IconSprite.new(cursor_x, cursor_y, self.viewport)
- cursor_bitmap = Bitmap.new(cursor_width, cursor_height)
- cursor_bitmap.stretch_blt(
- Rect.new(0, 0, cursor_width, cursor_height),
- @selarrow.bitmap,
- Rect.new(0, 0, @selarrow.bitmap.width, @selarrow.bitmap.height)
- )
- @cursor_sprite.bitmap = cursor_bitmap
- @cursor_sprite.z = self.z + 1 # Draw above window
- @cursor_sprite.visible = true # Ensure it's visible
- end
- # Override update to prevent built-in navigation
- def update
- # Don't call super - this prevents built-in input handling
- # Only update the animated cursor bitmap
- if @selarrow
- @selarrow.update
- end
- end
- def setup_font(bitmap)
- pbSetSystemFont(bitmap)
- if BASE_FONT_SIZE != 0
- bitmap.font.size += BASE_FONT_SIZE
- end
- end
- # FIXED: Account for text offset in cursor positioning
- def update_cursor_sprite
- @cursor_sprite&.dispose
- return if @index < 0
- # Use the same calculation as itemRect for consistency
- rect = itemRect(@index)
- cursor_x = self.x + rect.x
- cursor_y = self.y + rect.y + self.borderY + RECIPE_TEXT_Y_OFFSET # Add text offset
- cursor_width = rect.width
- cursor_height = rect.height
- # DEBUG: Print detailed positioning info
- puts "=== CURSOR DEBUG ==="
- puts "Index: #{@index}"
- puts "Window position: (#{self.x}, #{self.y})"
- puts "ItemRect: #{rect.x}, #{rect.y}, #{rect.width}, #{rect.height}"
- puts "BorderY: #{self.borderY}"
- puts "Text Y offset: #{RECIPE_TEXT_Y_OFFSET}"
- puts "Final cursor position: (#{cursor_x}, #{cursor_y})"
- puts "Top item: #{self.top_item}"
- puts "Row height: #{@row_height}"
- puts "===================="
- # Create cursor sprite that matches the item rectangle exactly
- @cursor_sprite = IconSprite.new(cursor_x, cursor_y, self.viewport)
- cursor_bitmap = Bitmap.new(cursor_width, cursor_height)
- cursor_bitmap.stretch_blt(
- Rect.new(0, 0, cursor_width, cursor_height),
- @selarrow.bitmap,
- Rect.new(0, 0, @selarrow.bitmap.width, @selarrow.bitmap.height)
- )
- @cursor_sprite.bitmap = cursor_bitmap
- @cursor_sprite.z = self.z + 1 # Draw above window
- @cursor_sprite.visible = true # Ensure it's visible
- end
- # FIXED: Match Pokemon Essentials bag logic exactly
- def draw_all_visible_items
- @item_max.times do |i|
- next if i < self.top_item - 1 || i > self.top_item + self.page_item_max # FIXED: Match bag logic
- drawItem(i, @item_max, itemRect(i))
- end
- end
- def update_recipe_list
- @recipes = CraftingRecipeManager.recipes_by_category(@current_category) || []
- @item_max = itemCount
- # Ensure index stays within bounds
- max_index = [itemCount - 1, 0].max
- self.index = [self.index, max_index].min
- self.index = [self.index, 0].max
- refresh
- end
- def index=(new_index)
- old_index = @index
- # Only allow changes from specific safe sources
- caller_info = caller[0..1].join(" <- ")
- puts "INDEX SETTER: #{old_index} → #{new_index} called by: #{caller_info}"
- super(new_index)
- if @index != old_index
- puts "INDEX: Actually changed from #{old_index} to #{@index}"
- update_cursor_sprite
- end
- end
- def current_category=(new_category)
- return if @current_category == new_category
- @current_category = new_category
- update_recipe_list
- end
- end
- #===============================================================================
- # Crafting Scene (Visual Management) - Enhanced with Ingredients Display
- #===============================================================================
- class CraftingScene
- include CraftingUIConfig
- def initialize
- @current_category = "All Items"
- @cached_graphics = {} # Cache for loaded graphics
- end
- def pbStartScene
- create_viewport
- create_background_sprites
- create_windows
- create_overlays
- update_arrow_visibility
- pbRefresh
- pbFadeInAndShow(@sprites)
- end
- def pbEndScene
- pbFadeOutAndHide(@sprites)
- dispose
- end
- def dispose
- dispose_cached_graphics
- pbDisposeSpriteHash(@sprites)
- @viewport&.dispose
- end
- # === INITIALIZATION METHODS ===
- private
- def create_viewport
- @viewport = Viewport.new(0, 0, Graphics.width, Graphics.height)
- @viewport.z = 99999
- @sprites = {}
- end
- def create_background_sprites
- # Main background
- @sprites["background"] = IconSprite.new(0, 0, @viewport)
- @sprites["background"].setBitmap(BACKGROUND_GRAPHIC)
- @sprites["background"].z = 0
- # Window backgrounds
- create_window_backgrounds
- create_navigation_arrows
- end
- def create_window_backgrounds
- @sprites["ingredients_bg"] = IconSprite.new(INGREDIENTS_WINDOW_X, INGREDIENTS_WINDOW_Y, @viewport)
- @sprites["ingredients_bg"].setBitmap(INGREDIENTS_BG_GRAPHIC)
- @sprites["ingredients_bg"].z = 1
- @sprites["recipe_bg"] = IconSprite.new(RECIPE_WINDOW_X, RECIPE_WINDOW_Y, @viewport)
- @sprites["recipe_bg"].setBitmap(RECIPE_BG_GRAPHIC)
- @sprites["recipe_bg"].z = 1
- @sprites["category_bg"] = IconSprite.new(0, CATEGORY_WINDOW_Y, @viewport)
- @sprites["category_bg"].setBitmap(CATEGORY_BG_GRAPHIC)
- @sprites["category_bg"].z = 1
- @sprites["description_bg"] = IconSprite.new(0, DESC_WINDOW_Y, @viewport)
- @sprites["description_bg"].setBitmap(DESC_BG_GRAPHIC)
- @sprites["description_bg"].z = 1
- center_category_window
- end
- def create_navigation_arrows
- @sprites["arrow_left"] = IconSprite.new(ARROW_LEFT_X, CATEGORY_WINDOW_Y, @viewport)
- @sprites["arrow_left"].setBitmap(ARROW_LEFT_GRAPHIC)
- @sprites["arrow_left"].z = 2
- @sprites["arrow_right"] = IconSprite.new(ARROW_RIGHT_X, CATEGORY_WINDOW_Y, @viewport)
- @sprites["arrow_right"].setBitmap(ARROW_RIGHT_GRAPHIC)
- @sprites["arrow_right"].z = 2
- @sprites["arrow_up"] = IconSprite.new(ARROW_UP_X, ARROW_UP_Y, @viewport)
- @sprites["arrow_up"].setBitmap(ARROW_UP_GRAPHIC)
- @sprites["arrow_up"].z = 2
- end
- def create_windows
- # Recipe list window with configurable positioning
- window_x = RECIPE_WINDOW_X + RECIPE_WINDOW_PADDING
- window_y = RECIPE_WINDOW_Y + RECIPE_WINDOW_Y_OFFSET
- window_w = RECIPE_WINDOW_WIDTH - RECIPE_WINDOW_PADDING * 2
- window_h = RECIPE_WINDOW_HEIGHT - RECIPE_WINDOW_HEIGHT_OFFSET
- @sprites["recipelist"] = Window_CraftingRecipeList.new(window_x, window_y, window_w, window_h)
- @sprites["recipelist"].viewport = @viewport
- @sprites["recipelist"].z = 2
- @sprites["recipelist"].current_category = @current_category
- # Connect the scene to the window for ingredient updates
- @sprites["recipelist"].instance_variable_set(:@scene, self)
- end
- def create_overlays
- # Create overlays with proper font setup
- ["ingredients_overlay", "result_item_overlay", "overlay"].each do |overlay_name|
- @sprites[overlay_name] = BitmapSprite.new(Graphics.width, Graphics.height, @viewport)
- setup_overlay_font(@sprites[overlay_name].bitmap)
- @sprites[overlay_name].z = 3
- end
- end
- def setup_overlay_font(bitmap)
- pbSetSystemFont(bitmap)
- if BASE_FONT_SIZE != 0
- bitmap.font.size += BASE_FONT_SIZE
- end
- end
- # === GRAPHICS MANAGEMENT ===
- def load_item_graphic(item_symbol)
- # Return cached graphic if available
- return @cached_graphics[item_symbol] if @cached_graphics[item_symbol]
- item_name = item_symbol.to_s
- graphic_paths = generate_graphic_paths(item_name)
- # Try to load from available paths
- graphic_paths.each do |path|
- if File.exist?(path)
- @cached_graphics[item_symbol] = Bitmap.new(path)
- return @cached_graphics[item_symbol]
- end
- end
- # Create simple fallback graphic
- @cached_graphics[item_symbol] = create_fallback_graphic
- return @cached_graphics[item_symbol]
- end
- def generate_graphic_paths(item_name)
- base_path = ITEM_GRAPHICS_PATH
- [
- "#{base_path}#{item_name}.png",
- "#{base_path}#{item_name.downcase}.png",
- "#{base_path}#{item_name.capitalize}.png",
- "#{base_path}#{item_name}.PNG"
- ]
- end
- def create_fallback_graphic
- fallback = Bitmap.new(32, 32)
- fallback.fill_rect(0, 0, 32, 32, Color.new(200, 200, 200))
- fallback
- end
- def dispose_cached_graphics
- return unless @cached_graphics
- @cached_graphics.each_value(&:dispose)
- @cached_graphics.clear
- end
- # === DISPLAY UPDATE METHODS ===
- public
- def update_ingredients_display
- clear_overlays
- recipe = current_recipe
- draw_ingredients_title
- return unless recipe && recipe != :cancel && recipe.is_a?(CraftingRecipe)
- draw_ingredients_list(recipe)
- draw_result_item(recipe)
- end
- private
- def clear_overlays
- @sprites["ingredients_overlay"].bitmap.clear
- @sprites["result_item_overlay"].bitmap.clear
- # Ensure fonts are properly set
- setup_overlay_font(@sprites["ingredients_overlay"].bitmap)
- setup_overlay_font(@sprites["result_item_overlay"].bitmap)
- end
- def draw_ingredients_title
- # Calculate centered position for title
- ingredients_window_width = get_ingredients_window_width
- title_x = INGREDIENTS_WINDOW_X + (ingredients_window_width / 2)
- title_y = INGREDIENTS_WINDOW_Y + INGREDIENTS_TITLE_Y_OFFSET
- textpos = [["Ingredients", title_x, title_y, :center, COLOR_TITLE, COLOR_TEXT]]
- pbDrawTextPositions(@sprites["ingredients_overlay"].bitmap, textpos)
- end
- def get_ingredients_window_width
- begin
- temp_bitmap = Bitmap.new(INGREDIENTS_BG_GRAPHIC)
- width = temp_bitmap.width
- temp_bitmap.dispose
- width
- rescue
- 180 # Fallback width
- end
- end
- def draw_ingredients_list(recipe)
- ingredients_x = INGREDIENTS_WINDOW_X + INGREDIENTS_WINDOW_PADDING
- ingredients_y = INGREDIENTS_WINDOW_Y + INGREDIENTS_LIST_Y_OFFSET
- recipe.ingredients.each_with_index do |(item_symbol, quantity), index|
- y_pos = ingredients_y + (index * INGREDIENT_SPACING)
- draw_single_ingredient(item_symbol, quantity, ingredients_x, y_pos)
- end
- end
- def draw_single_ingredient(item_symbol, quantity, x, y)
- # Draw ingredient icon (scaled and positioned)
- draw_ingredient_icon(item_symbol, x, y)
- # Draw ingredient text with availability coloring
- draw_ingredient_text(item_symbol, quantity, x + INGREDIENT_TEXT_X_OFFSET, y)
- end
- def draw_ingredient_icon(item_symbol, x, y)
- item_graphic = load_item_graphic(item_symbol)
- return unless item_graphic
- scaled_graphic = create_scaled_bitmap(item_graphic, INGREDIENT_ICON_SIZE, INGREDIENT_ICON_SIZE)
- @sprites["ingredients_overlay"].bitmap.blt(x, y, scaled_graphic,
- Rect.new(0, 0, INGREDIENT_ICON_SIZE, INGREDIENT_ICON_SIZE))
- scaled_graphic.dispose # Only dispose the scaled version
- end
- def draw_ingredient_text(item_symbol, quantity, x, y)
- item_name = get_item_name(item_symbol)
- player_quantity = get_player_quantity(item_symbol)
- # Determine text color based on availability
- text_color = player_quantity >= quantity ? COLOR_AVAILABLE : COLOR_MISSING
- ingredient_text = "#{item_name} x#{quantity}"
- # Temporarily adjust font size
- with_smaller_font do
- textpos = [[ingredient_text, x, y + INGREDIENT_TEXT_Y_OFFSET, :left, text_color, COLOR_TEXT]]
- pbDrawTextPositions(@sprites["ingredients_overlay"].bitmap, textpos)
- end
- end
- def draw_result_item(recipe)
- # Draw result item icon
- result_graphic = load_item_graphic(recipe.result_item)
- return unless result_graphic
- scaled_result = create_scaled_bitmap(result_graphic, RESULT_ITEM_ICON_SIZE, RESULT_ITEM_ICON_SIZE)
- @sprites["result_item_overlay"].bitmap.blt(RESULT_ITEM_PANEL_X, RESULT_ITEM_PANEL_Y,
- scaled_result, Rect.new(0, 0, RESULT_ITEM_ICON_SIZE, RESULT_ITEM_ICON_SIZE))
- scaled_result.dispose
- # Draw result item text and description
- draw_result_item_text(recipe)
- end
- def draw_result_item_text(recipe)
- result_name = get_item_name(recipe.result_item)
- result_desc = get_item_description(recipe.result_item)
- # Position text relative to icon
- text_x = RESULT_ITEM_PANEL_X + RESULT_TEXT_X_OFFSET
- name_and_qty = "#{result_name} x#{recipe.result_quantity}"
- textpos = [[name_and_qty, text_x, RESULT_TEXT_Y, :left, COLOR_TEXT, COLOR_SHADOW]]
- # Add word-wrapped description
- desc_y = RESULT_TEXT_Y + RESULT_DESC_Y_OFFSET
- add_wrapped_description(textpos, result_desc, text_x, desc_y)
- pbDrawTextPositions(@sprites["result_item_overlay"].bitmap, textpos)
- end
- def add_wrapped_description(textpos, description, x, start_y)
- if description.length > RESULT_DESC_WRAP_THRESHOLD
- desc_lines = wrap_text(description, RESULT_DESC_MAX_CHARS)
- desc_lines.each_with_index do |line, idx|
- y_pos = start_y + (idx * RESULT_DESC_LINE_HEIGHT)
- textpos << [line, x, y_pos, :left, COLOR_TEXT, COLOR_SHADOW]
- end
- else
- textpos << [description, x, start_y, :left, COLOR_TEXT, COLOR_SHADOW]
- end
- end
- # === UTILITY METHODS ===
- def create_scaled_bitmap(source, width, height)
- scaled = Bitmap.new(width, height)
- scaled.stretch_blt(Rect.new(0, 0, width, height), source,
- Rect.new(0, 0, source.width, source.height))
- scaled
- end
- def with_smaller_font
- overlay = @sprites["ingredients_overlay"].bitmap
- old_size = overlay.font.size
- new_size = [old_size - INGREDIENT_FONT_SIZE_REDUCTION, INGREDIENT_MIN_FONT_SIZE].max
- overlay.font.size = new_size
- yield
- overlay.font.size = old_size
- end
- def wrap_text(text, max_chars)
- words = text.split(' ')
- lines = []
- current_line = ""
- words.each do |word|
- if (current_line + word).length > max_chars
- lines << current_line.strip unless current_line.strip.empty?
- current_line = word + " "
- else
- current_line += word + " "
- end
- end
- lines << current_line.strip unless current_line.strip.empty?
- lines
- end
- def get_item_name(item_symbol)
- GameData::Item.get(item_symbol).name
- rescue
- item_symbol.to_s.capitalize
- end
- def get_item_description(item_symbol)
- GameData::Item.get(item_symbol).description || "A crafted item."
- rescue
- "A crafted item."
- end
- def get_player_quantity(item_symbol)
- $bag.quantity(item_symbol)
- rescue
- 0
- end
- # === PUBLIC INTERFACE METHODS ===
- public
- def pbRefresh
- # FIXED: Only refresh the recipe list if it's not currently being navigated
- @sprites["recipelist"]&.refresh
- update_arrow_visibility
- update_category_text
- update_ingredients_display
- end
- def update_category_text
- return unless @sprites["overlay"]
- @sprites["overlay"].bitmap.clear
- setup_overlay_font(@sprites["overlay"].bitmap)
- # Center category text in window with configurable offset
- category_x, category_y = get_category_position
- category_width, category_height = get_category_dimensions
- text_x = category_x + (category_width / 2)
- text_y = category_y + (category_height / 2) + CATEGORY_TEXT_Y_OFFSET
- textpos = [[@current_category, text_x, text_y, :center, COLOR_TEXT, COLOR_SHADOW]]
- pbDrawTextPositions(@sprites["overlay"].bitmap, textpos)
- end
- def center_category_window
- ingredients_center = INGREDIENTS_WINDOW_X + (get_ingredients_window_width / 2)
- category_width = get_category_dimensions[0]
- @sprites["category_bg"].x = (ingredients_center - (category_width / 2)).to_i
- end
- def update_arrow_visibility
- # Set arrow opacity based on current category position using configurable values
- case @current_category
- when "All Items"
- set_arrow_opacity(ARROW_OPACITY_DIM, ARROW_OPACITY_BRIGHT) # Left dim, right bright
- when "Pokéballs"
- set_arrow_opacity(ARROW_OPACITY_BRIGHT, ARROW_OPACITY_DIM) # Left bright, right dim
- else
- set_arrow_opacity(ARROW_OPACITY_BRIGHT, ARROW_OPACITY_BRIGHT) # Both bright
- end
- @sprites["arrow_up"].opacity = ARROW_OPACITY_BRIGHT
- end
- def switch_category(direction)
- old_category = @current_category
- @current_category = case direction
- when :left
- case @current_category
- when "Candies" then "All Items"
- when "Potions" then "Candies"
- when "Pokéballs" then "Potions"
- else @current_category
- end
- when :right
- case @current_category
- when "All Items" then "Candies"
- when "Candies" then "Potions"
- when "Potions" then "Pokéballs"
- else @current_category
- end
- else
- @current_category
- end
- # Only update if category actually changed
- if @current_category != old_category
- # FIXED: Don't call pbRefresh during category switch - it interferes with navigation
- @sprites["recipelist"].current_category = @current_category
- update_arrow_visibility
- update_category_text
- update_ingredients_display # Only update ingredients, not full refresh
- end
- end
- def pbUpdate
- pbUpdateSpriteHash(@sprites)
- end
- def current_recipe
- @sprites["recipelist"].item
- end
- def current_category
- @current_category
- end
- def sprites
- @sprites
- end
- # === PRIVATE HELPER METHODS ===
- private
- def get_category_position
- [@sprites["category_bg"].x, @sprites["category_bg"].y]
- end
- def get_category_dimensions
- begin
- temp_bitmap = Bitmap.new(CATEGORY_BG_GRAPHIC)
- width, height = temp_bitmap.width, temp_bitmap.height
- temp_bitmap.dispose
- [width, height]
- rescue
- [80, 32] # Fallback dimensions
- end
- end
- def set_arrow_opacity(left_opacity, right_opacity)
- @sprites["arrow_left"].opacity = left_opacity
- @sprites["arrow_right"].opacity = right_opacity
- end
- end
- #===============================================================================
- # Crafting Screen (Input Handling) - Optimized
- #===============================================================================
- class CraftingScreen
- include CraftingUIConfig
- def initialize(scene)
- @scene = scene
- end
- def pbStartScreen
- @scene.pbStartScene
- @recipe_window = @scene.sprites["recipelist"]
- handle_input_loop
- @scene.pbEndScene
- end
- private
- def handle_input_loop
- loop do
- Graphics.update
- Input.update
- @scene.pbUpdate
- break if process_input
- end
- end
- def process_input
- if Input.trigger?(Input::UP)
- navigate_recipes(-1)
- elsif Input.trigger?(Input::DOWN)
- navigate_recipes(1)
- elsif Input.trigger?(Input::LEFT)
- @scene.switch_category(:left)
- pbPlayCursorSE
- elsif Input.trigger?(Input::RIGHT)
- @scene.switch_category(:right)
- pbPlayCursorSE
- elsif Input.trigger?(Input::USE)
- return true if handle_selection
- elsif Input.trigger?(Input::BACK)
- pbPlayCloseMenuSE
- return true
- end
- false # Continue loop
- end
- def navigate_recipes(direction)
- old_index = @recipe_window.index
- new_index = old_index + direction
- max_index = @recipe_window.itemCount - 1
- # Ensure new_index stays within bounds
- if new_index >= 0 && new_index <= max_index
- puts "NAVIGATION: Moving from #{old_index} to #{new_index}"
- # Set index directly without triggering the setter's protection
- @recipe_window.instance_variable_set(:@index, new_index)
- # Update cursor manually since we bypassed the setter
- @recipe_window.update_cursor_sprite
- # Update ingredients display
- @scene.update_ingredients_display
- pbPlayCursorSE
- else
- puts "NAVIGATION: Can't move to #{new_index} (out of bounds)"
- end
- end
- def handle_selection
- current_item = @scene.current_recipe
- # Handle cancel option
- if current_item == :cancel
- pbPlayCloseMenuSE
- return true
- end
- # Handle recipe crafting
- if current_item&.is_a?(CraftingRecipe)
- attempt_crafting(current_item)
- else
- pbPlayBuzzerSE
- end
- false
- end
- def attempt_crafting(recipe)
- if recipe.can_craft?
- pbPlayDecisionSE
- if pbConfirmMessage("Craft #{recipe.name}?")
- craft_recipe(recipe)
- end
- else
- pbPlayBuzzerSE
- show_crafting_error(recipe)
- end
- end
- def craft_recipe(recipe)
- if recipe.craft!
- pbMessage("Successfully crafted #{recipe.name}!")
- @recipe_window.refresh
- @scene.pbRefresh
- else
- pbPlayBuzzerSE
- pbMessage("Failed to craft #{recipe.name}!")
- end
- end
- def show_crafting_error(recipe)
- if recipe.unlocked?
- pbMessage("You don't have the required materials to craft #{recipe.name}!")
- else
- unlock_text = recipe.unlock_condition || "Complete certain requirements"
- pbMessage("This recipe is locked! #{unlock_text}")
- end
- end
- end
- #===============================================================================
- # Main entry point and backward compatibility
- #===============================================================================
- def pbCraftingMenu
- scene = CraftingScene.new
- screen = CraftingScreen.new(scene)
- screen.pbStartScreen
- end
- # Backward compatibility
- class Scene_Crafting
- def main
- pbCraftingMenu
- end
- end
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement