SLIP INTO RUBY - UNDER THE HOOD PART 18: MORE WINDOWS

In which gosh darn it I'm late again.

  • Trihan
  • 04/27/2017 06:46 PM
  • 1126 views
It's TuesdayThursday! Aside from being the day after Monday, that also means it's time for another exciting episode of



Today, we continue our foray into the window classes:

Window_EquipStatus
This is the window that displays the changes to actor stats when you're in the equipment menu.

def initialize(x, y)
    super(x, y, window_width, window_height)
    @actor = nil
    @temp_actor = nil
    refresh
  end


The constructor takes 2 parameters; the x and y position of the window. We call the super method passing in x, y, window_width and window_height. Then we set @actor and @temp_actor to nil, and refresh the window.

def window_width
    return 208
  end


The width of the equip status window is hardcoded at 208.

def window_height
    fitting_height(visible_line_number)
  end


The height of the window is the height needed to fit visible_line_number lines...

def visible_line_number
    return 7
  end


...and that returns 7.

def actor=(actor)
    return if @actor == actor
    @actor = actor
    refresh
  end


The actor assignment method takes one parameter (oddly enough, called actor). First, we return if @actor contains the same object as the actor parameter. Then we set @actor to that object, and refresh the window.

def refresh
    contents.clear
    draw_actor_name(@actor, 4, 0) if @actor
    6.times {|i| draw_item(0, line_height * (1 + i), 2 + i) }
  end


The refresh method clears the window contents, draws the actor's name at (4, 0) if an actor object is set, and then runs a 6-iteration loop using the iteration variable 'i' which draws the current item at x 0 and a y corresponding to the height of a line multiplied by the current iteration value + 1 (we add 1 to it because i will start at 0).

def set_temp_actor(temp_actor)
    return if @temp_actor == temp_actor
    @temp_actor = temp_actor
    refresh
  end


This method sets a temporary actor after equipment change (I'll cover this in the next method and also in the other equip windows). It takes one parameter, temp_actor. We return if @temp_actor contains the same object as the parameter value, then set @temp_actor to that value and refresh the window.

def draw_item(x, y, param_id)
    draw_param_name(x + 4, y, param_id)
    draw_current_param(x + 94, y, param_id) if @actor
    draw_right_arrow(x + 126, y)
    draw_new_param(x + 150, y, param_id) if @temp_actor
  end


The overloaded draw_item method takes three parameters: x position, y position, and parameter ID. We draw the parameter's name, 4 pixels to the right of the original x, and param_id as the parameter ID. Then we draw the parameter's current value 94 pixels to the right of the original x, but only if an actor object is set. After that we draw a right arrow 126 pixels to the right of the original x, and the new param value 150 pixels to the right of the original x, but only if a temp actor object is set.

Okay, so @temp_actor. You'll be accustomed by now, probably, to the RM feature where when you're selecting equipment items, it will show you the increases/decreases to the actor's main parameters if you equip that item. Rather than the equip window running through each trait of the item selected to calculate the gains/losses, it literally loads in a copy of the menu actor and equips the new item on that actor, then displays their new parameters.

def draw_param_name(x, y, param_id)
    change_color(system_color)
    draw_text(x, y, 80, line_height, Vocab::param(param_id))
  end


The method for drawing a parameter name takes x, y and param_id as parameters, just like draw_item does. We change the text colour to system_color, then draw the Vocab element returned by passing param_id to the param method (which gets the database name of the current parameter), with an allocated width of 80 pixels and allocated height of a single line.

def draw_current_param(x, y, param_id)
    change_color(normal_color)
    draw_text(x, y, 32, line_height, @actor.param(param_id), 2)
  end


Almost identical to the previous method, only this time we're changing to normal_color, the allocated width is 32 pixels rather than 80, and we're drawing the value returned by passing param_id to the actor object's param method.

def draw_right_arrow(x, y)
    change_color(system_color)
    draw_text(x, y, 22, line_height, "→", 1)
  end


The method for drawing the right arrow only takes x and y as parameters. Again we change colour to system_color, and then draw the arrow with an allocated width of 22 pixels, centre-aligned.

def draw_new_param(x, y, param_id)
    new_value = @temp_actor.param(param_id)
    change_color(param_change_color(new_value - @actor.param(param_id)))
    draw_text(x, y, 32, line_height, new_value, 2)
  end


The method for drawing the new parameter value takes the same parameters as the other param-drawing methods. First we set new_value to the value returned by passing param_id to the temp actor's param method, then change text colour to the result of calling param_change_color and passing in the difference between the new value and the current one (this will either change to green if the difference is positive, or red if the difference is negative; we looked at this way back when we covered Window_Base). Finally, we draw the new value at the passed-in coordinates with an allocated width of 32 pixels, right-aligned.

Window_Status
This is the window that shows the more in-depth status display for an actor, as seen when using the Status command in the menu.

def initialize(actor)
    super(0, 0, Graphics.width, Graphics.height)
    @actor = actor
    refresh
    activate
  end


Pretty standard constructor, though it does take actor as a parameter. We call super (which in this case is Window_Selectable) with coordinates of (0, 0), and width/height equal to the entire screen size. Then we set @actor to the parameter value, refresh the window and activate it.

def actor=(actor)
    return if @actor == actor
    @actor = actor
    refresh
  end


This is just the same kind of actor assignment method we've already seen. Takes actor as a parameter, returns if @actor contains the same object as the parameter does, sets @actor to the parameter value, and refreshes the window.

def refresh
    contents.clear
    draw_block1   (line_height * 0)
    draw_horz_line(line_height * 1)
    draw_block2   (line_height * 2)
    draw_horz_line(line_height * 6)
    draw_block3   (line_height * 7)
    draw_horz_line(line_height * 13)
    draw_block4   (line_height * 14)
  end


Refresh is essentially just a wrapped for calls to various block drawing methods. We clear the window contents, then draw block1 at the top of the window. We draw a horizontal line at y coordinate line_height, then block 2 at double line height, another horizontal line at 6x line height, block 3 at 7x line height, another horizontal line at 13x line height, and block 4 at 14x line height.

def draw_block1(y)
    draw_actor_name(@actor, 4, y)
    draw_actor_class(@actor, 128, y)
    draw_actor_nickname(@actor, 288, y)
  end


When we draw block 1, we draw the actor's name at X 4, class at X 128, and nickname at X 288.

def draw_block2(y)
    draw_actor_face(@actor, 8, y)
    draw_basic_info(136, y)
    draw_exp_info(304, y)
  end


In block 2, we draw the actor's face at X 8, their basic info at X 136, and exp info at X 304.

def draw_block3(y)
    draw_parameters(32, y)
    draw_equipments(288, y)
  end


In block 3, we draw the actor's parameters at X 32 and their equipment at X 288.

def draw_block4(y)
    draw_description(4, y)
  end


Finally in block 4, we draw the actor's description at X 4.

def draw_horz_line(y)
    line_y = y + line_height / 2 - 1
    contents.fill_rect(0, line_y, contents_width, 2, line_color)
  end


To draw a horizontal line, we set line_y to the passed-in y, plus half the height of a line, minus 1. Then we fill a rect contents_width pixels wide and 2 pixels high in line_color.

def line_color
    color = normal_color
    color.alpha = 48
    color
  end


The line color is normal_color but with an alpha of 48.

def draw_basic_info(x, y)
    draw_actor_level(@actor, x, y + line_height * 0)
    draw_actor_icons(@actor, x, y + line_height * 1)
    draw_actor_hp(@actor, x, y + line_height * 2)
    draw_actor_mp(@actor, x, y + line_height * 3)
  end


To draw the actor's basic info, we draw their level, icons, hp and mp. Everything is drawn relative to the passed-in x and y coordinates, with level at the given y, icons at y + line height, hp at y + twice line height, and mp at y + three times line height.

def draw_parameters(x, y)
    6.times {|i| draw_actor_param(@actor, x, y + line_height * i, i + 2) }
  end


To draw the actor's parameters, we run a 6-iteration loop using iteration variable "i" and call draw_actor_param passing in the actor, x, calculated y and parameter ID (we start at 2 because HP and MP are drawn separately).

def draw_exp_info(x, y)
    s1 = @actor.max_level? ? "-------" : @actor.exp
    s2 = @actor.max_level? ? "-------" : @actor.next_level_exp - @actor.exp
    s_next = sprintf(Vocab::ExpNext, Vocab::level)
    change_color(system_color)
    draw_text(x, y + line_height * 0, 180, line_height, Vocab::ExpTotal)
    draw_text(x, y + line_height * 2, 180, line_height, s_next)
    change_color(normal_color)
    draw_text(x, y + line_height * 1, 180, line_height, s1, 2)
    draw_text(x, y + line_height * 3, 180, line_height, s2, 2)
  end


To draw the actor's exp info, we set s1 to a string of dashes if the actor is max level, or their level otherwise. Then we set s2 to a string of dashes if the actor is max level, or their next level exp minus their exp otherwise (in other words, how much exp they need to level up). We set s_next to an sprintf passing in Vocab::ExpNext and Vocab::level. If you remember when we looked at Vocab (or have a look at the scripts now), ExpNext is "To next %s"; This is the string that sprintf will replace with the passed-in arguments, in this case Vocab::level (which in the default database is "Level", which will be inserted in place of the %s.

def draw_equipments(x, y)
    @actor.equips.each_with_index do |item, i|
      draw_item_name(item, x, y + line_height * i)
    end
  end


To draw the actor's equipment, we iterate through the actor's equips with an index using the iteration variables "item" and "i", and in each iteration we call draw_item_name, passing in the item and coordinates.

def draw_description(x, y)
    draw_text_ex(x, y, @actor.description)
  end


To draw the actor's description, we call draw_text_ex passing in the given coordinates the the actor's description property.

Window_SaveFile
This is the window that shows save files on the save/load screens. It has one public instance variable:

attr_reader   :selected                 # selected

selected is an attr_reader which basically just determines whether the file should be highlighted.

def initialize(height, index)
    super(0, index * height, Graphics.width, height)
    @file_index = index
    refresh
    @selected = false
  end


Pretty standard constructor, taking height and index as parameters. We call the super method passing in the x, y, width and height arguments as usual, then set @file_index to the passed-in index, refresh the window, and set @selected to false.

def refresh
    contents.clear
    change_color(normal_color)
    name = Vocab::File + " #{@file_index + 1}"
    draw_text(4, 0, 200, line_height, name)
    @name_width = text_size(name).width
    draw_party_characters(152, 58)
    draw_playtime(0, contents.height - line_height, contents.width - 4, 2)
  end


When we refresh a save file window, we clear the contents as with most other windows. We change the text colour to normal_color, and set a temp variable called name to the vocab set for files plus @file_index + 1 (because it starts at 0 but we want the actual display to start at 1). Then we draw the filename at (4, 0) with an allotted width of 200 pixels. @name_width is set to the required width for drawing name, then we call draw_party_characters passing in an X of 152 and a Y of 58, and finally we call draw_playtime passing in an X of 0, a Y of the contents height minus the height of a line, a width of the contents width - 4, and an align of 2 (right).

def draw_party_characters(x, y)
    header = DataManager.load_header(@file_index)
    return unless header
    header[:characters].each_with_index do |data, i|
      draw_character(data[0], data[1], x + i * 48, y)
    end
  end


This method draws the characters in the current party and takes two parameters: x and y. First we set header to the result of calling DataManager's load_header method passing in @file_index. This loads the save file contents. We then return unless header contains data, because otherwise there are no save file contents to reference. We iterate through each member of the header's :characters key with an index, using the iteration variable "data" and index variable "i", then call draw_character passing in data (the character graphic), data (the character index) x + 48 * the index (so each character is 48 pixels to the right of the last one) and y.

def draw_playtime(x, y, width, align)
    header = DataManager.load_header(@file_index)
    return unless header
    draw_text(x, y, width, line_height, header[:playtime_s], 2)
  end


This method draws the amount of time played, taking four parameters: x, y, width, and alignment. As before, we load the file header into a variable called header and return unless it contains data. Then we call draw_text, passing in the given x and y coordinates, width, the height of a line, and the :playtime_s key from header, with an align value of 2 (right).

def selected=(selected)
    @selected = selected
    update_cursor
  end


This method is used to change which save file is selected in the window. It takes selected as a parameter. We set @selected to the passed-in value, then call update_cursor.

def update_cursor
    if @selected
      cursor_rect.set(0, 0, @name_width + 8, line_height)
    else
      cursor_rect.empty
    end
  end


This method updates the cursor in the window. If @selected is true, we set the cursor rect starting at (0, 0) and ending at (@name_width + 8, line_height) which basically creates a highlight rect covering the whole window contents. Otherwise, we set the cursor rect to empty so that nothing is highlighted.

Window_ShopCommand
Pretty short one, this. It's the window that allows you to select "Buy", "Sell" and "Cancel" on a shop screen.

def initialize(window_width, purchase_only)
    @window_width = window_width
    @purchase_only = purchase_only
    super(0, 0)
  end


The constructor takes two parameters: window_width (the width of the window, obviously) and purchase_only, a flag determining whether the player can sell items to the shop or not. We set the appropriate instance variables, then call the super method (as this window inherits from Window_HorzCommand, we need only supply arguments for X and Y coordinates).

def window_width
    @window_width
  end


This method is just a getter for @window_width, and returns its value.

def col_max
    return 3
  end


Since we have 3 commands and it's a horizontal command window, it stands to reason that the max number of columns would be 3.

def make_command_list
    add_command(Vocab::ShopBuy,    :buy)
    add_command(Vocab::ShopSell,   :sell,   !@purchase_only)
    add_command(Vocab::ShopCancel, :cancel)
  end


Not much here that we haven't covered in other command windows. Ad add the buy, sell and cancel commands, using the appropriate Vocab properties and symbols. The sell command has an additional enabled parameter, which is true if @purchase_only is false, and false otherwise. This will fade out the sell option if the player can't sell items at the shop.

Window_ShopBuy
This is the window that shows the list of buyable items on the shop screen. It inherits from Window_Selectable and has one public instance variable:

attr_reader   :status_window            # Status window


:status_window is an attr_reader which holds the "shop status window" object. This is used to create a link between the buy/sell windows and the one that shows possessed items/equipment, as we'll see in a few windows' time.

def initialize(x, y, height, shop_goods)
    super(x, y, window_width, height)
    @shop_goods = shop_goods
    @money = 0
    refresh
    select(0)
  end


The constructor takes four parameters: x and y coordinates, window height, and an array of shop goods. We call the super method passing in the coordinate parameters, result of the window_width method, and passed-in height. Then we set @shop_goods to the passed-in shop_goods parameter, set another instance variable called @money to 0, refresh the window, and select the first item.

def window_width
    return 304
  end


window_width is hardcoded to return 304, so the window will be 304 pixels wide.

def item_max
    @data ? @data.size : 1
  end


The method for determining the maximum number of items returns the size of @data if it contains values, and 1 otherwise.

def item
    @data[index]
  end


The item method returns the element of @data at the current index.

def money=(money)
    @money = money
    refresh
  end


This method sets the amount of gold the party possesses (as a reference for the shop scene, it doesn't affect the party's actual gold), taking money as a parameter. We set @money to the passed value, then refresh the window.

def current_item_enabled?
    enable?(@data[index])
  end


We've seen this one before. This method determines whether the current item is enabled, returning the result of calling the enable? method and passing in the element of @data at the current index.

def price(item)
    @price[item]
  end


This method gets the price of an item, taking item as a parameter. Returns the value of the key matching item in the @price hash.

def enable?(item)
    item && price(item) <= @money && !$game_party.item_max?(item)
  end


The enable? method takes item as a parameter; it returns true if item is not nil AND the price of the item is less than or equal to @money AND the party doesn't have the maximum number of the item already.

def refresh
    make_item_list
    create_contents
    draw_all_items
  end


The refresh method we've seen before as well. We make the item list, create the window contents, then draw all items. Nothing new here.

def make_item_list
    @data = []
    @price = {}
    @shop_goods.each do |goods|
      case goods[0]
      when 0;  item = $data_items[goods[1]]
      when 1;  item = $data_weapons[goods[1]]
      when 2;  item = $data_armors[goods[1]]
      end
      if item
        @data.push(item)
        @price[item] = goods[2] == 0 ? item.price : goods[3]
      end
    end
  end


To create the item list, first we initialise a couple of variables: @data is set to an empty array, and @price to an empty hash. Then we run an each loop on @shop_goods, using the iteration variable "goods". Inside the loop, we run a case statement on the first element of goods (which is an integer determining the item type): when it's 0, we use $data_items; when it's 1, we use $data_weapons; and when it's 2, we use $data_armors. In each case, we set a temporary variable called "item" and use goods as the index (which holds the index of the item as defined in the database).

After the case statement, if item is not nil, we push item to the @data array and then set the value of the @price hash with a key matching item (basically, the key will be an item, weapon or armor object) to the price property of item if goods is equal to 0 (this will either be 0 for standard price, or 1 if the price was specified) and goods otherwise, which contains the specific price that was set.

def draw_item(index)
    item = @data[index]
    rect = item_rect(index)
    draw_item_name(item, rect.x, rect.y, enable?(item))
    rect.width -= 4
    draw_text(rect, price(item), 2)
  end


As with all other versions of draw_item, we pass in index as a parameter. We set item to the element of @data at index, and rect to the item rect returned when index is passed in. We draw the item name at the rect's coordinates, determining its enabled state by passing in the result of calling enable? passing in item. We reduce the rect's width by 4 (as explained previously, this is to make the price line up better with the highlight) and then draw the price of the item aligned to the right.

def status_window=(status_window)
    @status_window = status_window
    call_update_help
  end


This method sets the associated status window, taking status_window as a parameter. We set @status_window to the parameter value, then update the help.

def update_help
    @help_window.set_item(item) if @help_window
    @status_window.item = item if @status_window
  end


This method updates the help text for the highlighted item. Calls the set_item method of @help_window passing in item if @help_window is not nil, then calls the item= method of @status_window passing in item if @status_window is not nil.

Window_ShopSell
This window displays a list of items held by the party which the player can sell to a shop. It inherits from Window_ItemList.

def initialize(x, y, width, height)
    super(x, y, width, height)
  end


The simplest of constructors. It does nothing but call the super method.

def current_item_enabled?
    enable?(@data[index])
  end


Again, nothing new here. The method to determine whether the current item is enabled just calls enable? and passes in the element of @data at the current index.

def enable?(item)
    item && item.price > 0
  end


And for checking whether the item is enabled, we return true if the item is not nil AND its price is greater than 0.

Window_ShopNumber
This window is for entering the quantity of items to buy or sell in a shop. It inherits from Window_Selectable and has one public instance variable:

attr_reader   :number                   # quantity entered


:number is an attr_reader storing the quantity the player entered.

def initialize(x, y, height)
    super(x, y, window_width, height)
    @item = nil
    @max = 1
    @price = 0
    @number = 1
    @currency_unit = Vocab::currency_unit
  end


The constructor takes x, y and height as parameters. First we call the super method, passing in the coordinates, width and height. Then we set @item to nil, @max to 1, @price to 0, @number to 1 and @currency_unit to whatever the player entered for currency unit in the database.

def window_width
    return 304
  end


Again, window_width is hardcoded at 304 pixels wide.

def set(item, max, price, currency_unit = nil)
    @item = item
    @max = max
    @price = price
    @currency_unit = currency_unit if currency_unit
    @number = 1
    refresh
  end


This method is for setting the various instance variables of the window, taking four possible parameters: item, max, price and currency_unit. currency_unit is optional, and will default to nil if no value is passed in. We set @item to item, @max to max, @price to price, @currency_unit to currency_unit (if one was passed in), @number to 1, and then we refresh the window.

def currency_unit=(currency_unit)
    @currency_unit = currency_unit
    refresh
  end


This method sets the currency unit, taking currency_unit as a parameter. We set @currency_unit to the parameter value and then refresh the window.

def refresh
    contents.clear
    draw_item_name(@item, 0, item_y)
    draw_number
    draw_total_price
  end


The refresh method clears the contents as usual, then draws the name of the item at (0, item_y), draws the number entered by the player, and then draws the total price of the items being bought/sold.

def draw_number
    change_color(normal_color)
    draw_text(cursor_x - 28, item_y, 22, line_height, "×")
    draw_text(cursor_x, item_y, cursor_width - 4, line_height, @number, 2)
  end


This method draws the number entered by the player. We change the text colour to normal_color, then draw a small × symbol 28 pixels to the left of the cursor X coordinate, then draw the held number of the item (right-aligned) at the cursor X.

def draw_total_price
    width = contents_width - 8
    draw_currency_value(@price * @number, @currency_unit, 4, price_y, width)
  end


This method draws the total price of items being bought/sold. We set width to the contents width minus 8, then draw the currency value passing in @price multiplied by @number as the value.

def item_y
    contents_height / 2 - line_height * 3 / 2
  end


item_y returns the y at which the number should be drawn, returning half the contents height minus 1.5 times the height of a line. Simple, eh?

def price_y
    contents_height / 2 + line_height / 2
  end


This method gets the y at which the price should be drawn, returning half the contents height plus half the height of a line.

def cursor_width
    figures * 10 + 12
  end


cursor_width is returned as 10 times the maximum number of digits in the quantity display plus 12. (each digit takes 10 pixels, and we add 12 to give 6 pixels of 'padding' either side of the cursor)

def cursor_x
    contents_width - cursor_width - 4
  end


The x coordinate of the cursor is returned as the contents width minus cursor width minus 4. This will end up putting the cursor on the far right of the window with 4 pixels of padding to the right.

def figures
    return 2
  end


The maximum number of digits in the quantity display is hardcoded at 2, as 99 is the maximum value the player can enter.

def update
    super
    if active
      last_number = @number
      update_number
      if @number != last_number
        Sound.play_cursor
        refresh
      end
    end
  end


The frame update method for the number window first calls the super method. Then, if the window is active, we set last_number to the value in @number, call the update_number method, then if @number is different from last_number we play the cursor sound effect and refresh the window. (this means that every frame the window monitors for the value changing, and if it did the cursor sound is played and the window display is updated).

def update_number
    change_number(1)   if Input.repeat?(:RIGHT)
    change_number(-1)  if Input.repeat?(:LEFT)
    change_number(10)  if Input.repeat?(:UP)
    change_number(-10) if Input.repeat?(:DOWN)
  end


This is the number update method which changes the buy/sell quantity in accordance with player input. If the player holds right, we call change_number passing in 1. If they hold left, we call it and pass in -1. If they hold up, we pass in 10, and if they hold down we pass in -10.

def change_number(amount)
    @number = [[@number + amount, @max].min, 1].max
  end


This method changes the window number by the amount specified in the parameter. @number is set to the maximum value between 1 and the minimum value between @max and @number + amount. This will result in the number never being lower than 1 or higher than @max.

def update_cursor
    cursor_rect.set(cursor_x, item_y, cursor_width, line_height)
  end


This method updates the cursor. We call the set method on cursor_rect passing in the cursor's x, the item_y value, cursor_width and line_height. We need to overwrite this because the cursor in the quantity window needs to be a different size from the standard cursor.

Window_ShopStatus
This window displays the number of items possessed by the party and an actor's current equipment (if the highlighted item is equipment). It inherits from Window_Base.

def initialize(x, y, width, height)
    super(x, y, width, height)
    @item = nil
    @page_index = 0
    refresh
  end


The constructor takes the usual parameters and calls the super method as normal. We set @item to nil, @page_index to 0, and then refresh the window.

def refresh
    contents.clear
    draw_possession(4, 0)
    draw_equip_info(4, line_height * 2) if @item.is_a?(RPG::EquipItem)
  end


This method refreshes the window. We clear the contents as usual, then draw the number of items in the party's possession at (4, 0) and then if @item is an RPG::EquipItem (which it will be if it's a weapon or armour) we draw the party's equip info at (4, double line height). This will draw the equip info 2 lines below the possessed quantity.

def item=(item)
    @item = item
    refresh
  end


This method sets the item in the window, taking item as a parameter. We set @item to the parameter value, then refresh the window.

def draw_possession(x, y)
    rect = Rect.new(x, y, contents.width - 4 - x, line_height)
    change_color(system_color)
    draw_text(rect, Vocab::Possession)
    change_color(normal_color)
    draw_text(rect, $game_party.item_number(@item), 2)
  end


This method draws the party's possessed quantity of the selected item, taking x and y as parameters. We set rect to a new Rect starting at (x, y) and finishing at (contents width - 4 - x, line height). This will give us a rect spanning from (4, 0) in the window to 8 pixels from the end of the window contents, the height of a single line.

We change the text colour to system_color, then draw the "Possession" string from the Vocab module, which by default is "Possession", funnily enough. Then we change the text colour to normal_color and draw the result of calling $game_party's item_number method with @item passed in (right-aligned).

def draw_equip_info(x, y)
    status_members.each_with_index do |actor, i|
      draw_actor_equip_info(x, y + line_height * (i * 2.4), actor)
    end
  end


This method draws the party's equipment info, taking x and y as parameters. We execute an each_with_index loop on status_members, using iteration variable "actor" and index variable "i". Inside the loop, we call draw_actor_equip_info passing in the coordinates and actor (the y coordinate is the parameter value plus 2.4 times index multiplied by line height). This will put a small amount of space between each actor after their equipped item is drawn.

def status_members
    $game_party.members[@page_index * page_size, page_size]
  end


status_members gets an array of actors for which equipment information should be drawn. Returns the elements of $game_party's members property ranging from index @page_index * page_size up to page_size. I'll explain in a second.

def page_size
    return 4
  end


page_size is hardcoded to return 4, as this is the maximum number of actors the status window can display at a time.

Okay, so one cool thing about Ruby is that you can return a subset from an array just by specifying the start index and number of elements you wish to return. So say @page_index is 0, status_members will return 4 members starting at 0 (0, 1, 2, 3). If we had, say, 7 party members and had pressed shift to move to the next page, @page_index would now be 1, so status_members would return 4 members starting at 4 (4, 5, 6).

def page_max
    ($game_party.members.size + page_size - 1) / page_size
  end


page_max gets the maximum number of pages required to show the entire party, returning (the number of party members plus page_size - 1) divided by page_size. So for example, if we have 7 party members it returns (7 + 3) / 4 = 2.5 which gets rounded to 2.

def draw_actor_equip_info(x, y, actor)
    enabled = actor.equippable?(@item)
    change_color(normal_color, enabled)
    draw_text(x, y, 112, line_height, actor.name)
    item1 = current_equipped_item(actor, @item.etype_id)
    draw_actor_param_change(x, y, actor, item1) if enabled
    draw_item_name(item1, x, y + line_height, enabled)
  end


This method draws the actor's equip information, taking x/y coordinates and the actor as parameters.

We set enabled to the result of calling the actor's equippable? method passing in @item. Then we change the text colour to normal_color and passing in enabled to determine whether to draw it semi-transparent (if the actor can't equip the highlighted item). We draw the actor's name at the parameter coordinates, then set item1 to the actor's currently-equipped item corresponding to the equipment type ID of @item, then draw the potential parameter change for the actor upon equipping the highlighted item if enabled is true, and then we draw the name of item1 a line below the actor's name.

def draw_actor_param_change(x, y, actor, item1)
    rect = Rect.new(x, y, contents.width - 4 - x, line_height)
    change = @item.params[param_id] - (item1 ? item1.params[param_id] : 0)
    change_color(param_change_color(change))
    draw_text(rect, sprintf("%+d", change), 2)
  end


This method draws the change in parameters that the highlighted equipment will give to the actor, taking the x/y coordinates, actor object, and item1 as parameters.

We set rect to a new Rect the same way as we did in draw_possession, then set change to the element of @item's params array at index param_id minus the same element of item1's array if item1 is not nil, and 0 otherwise. This will either give us the base increase of the item, or the difference between the item's parameter and that of the currently-equipped item.

def param_id
    @item.is_a?(RPG::Weapon) ? 2 : 3
  end


This method shows just how primitive VX Ace's standard shop equip status display is. If the @item is an RPG::Weapon, returns 2, otherwise returns 3. This means that by default the only statistics which will have their changes shown are ATK and DEF.

def current_equipped_item(actor, etype_id)
    list = []
    actor.equip_slots.each_with_index do |slot_etype_id, i|
      list.push(actor.equips[i]) if slot_etype_id == etype_id
    end
    list.min_by {|item| item ? item.params[param_id] : 0 }
  end


However, there are some things where it's slightly more clever. This method gets the current equipment, but will actually return the weaker item if there is more than one with the same equipment type as the highlighted one (this also applies to dual wield with highlighted weapons). It takes actor and etype_id as parameters.

First we set list to an empty array, then execute an each_with_index loop on the actor's equip_slots array, using iteration variable "slot_etype_id" and index variable "i". In the loop, we push the actor's equips at index i to list if the slot_etype_id variable is equal to etype_id. This will result in list being an array of all the actor's equipment that has the same equipment type ID as the highlighted item's.

Once we have that array, we call the min_by method on list, using the iteration variable "item". In the block, if item is not nil, we return the element of item's params array at param_id, and otherwise we return 0. This will return the item with the lowest parameter.

def update
    super
    update_page
  end


The frame update method for the window first calls the super method, then calls update_page:

def update_page
    if visible && Input.trigger?(:A) && page_max > 1
      @page_index = (@page_index + 1) % page_max
      refresh
    end
  end


This method updates the page based on player input. If the window is visible AND the player presses :A (shift) AND the max pages is greater than 1, we set @page_index to @page_index + 1 MOD page_max. This will increase the page index by 1 until it gets to the max number of pages, at which point it'll loop back around to page 0.

And so ends another issue! We're getting to the tail end of the window class breakdown now; there are 20 more classes to look at and then we finally get to the Scene classes.

As usual, comments are welcome. Until next time!