SLIP INTO RUBY - UNDER THE HOOD PART 17: WHEN I'M CLEANING WINDOWS!

In which the name doesn't have much to do with the actual content.

  • Trihan
  • 04/05/2017 12:19 PM
  • 260 views
Trihan here! You may have noticed that since the last episode I've gone through a bit of rebranding (my scripts, plugins, tutorials and articles are now released under the Trilobytes umbrella) so sit your ass down and prepare your body for the first Trilobytes episode of



Last time we looked at the command window classes, and now it's time to check out some of the other windows!

Window_Help
This is the window that shows descriptions of items and skills and offers other helpful information in various scenes. It has no public instance variables.

def initialize(line_number = 2)
    super(0, 0, Graphics.width, fitting_height(line_number))
  end


The constructor takes one parameter, line_number, which defaults to 2. All we do in this method is call the initialize method of the parent class, which is Window_Base, passing in X and Y coordinates of 0 and 0, the full window width as the width argument, and the fitting height for the passed-in number of lines as the height argument.

def set_text(text)
    if text != @text
      @text = text
      refresh
    end
  end


This method allows you to set the text that's shown in the window, and takes one parameter, text. First of all, we only do anything in this method if the passed-in text is different from the @text variable. If so, the passed-in text is assigned to @text, and we call the refresh method.

def clear
    set_text("")
  end


The clear method simply calls set_text passing in an empty string.

def set_item(item)
    set_text(item ? item.description : "")
  end


This method is used to set the help text to the description of an item/skill etc. and simply calls set_text with a ternary if statement: if an item was passed in, we return the item's description, otherwise a blank string.

def refresh
    contents.clear
    draw_text_ex(4, 0, @text)
  end


The refresh method clears the contents and then calls draw_text_ex passing in an x coordinate of 4, a y coordinate of 0, and @text. Readers with elephantine memories will remember way back when we looked at Window_Base that draw_text_ex is the one that processes text codes, meaning you can also use them in the help window.

Window_Gold
This is the window that shows how many doubloons your party has accumulated. As with the help window, it has no public instance variables.

def initialize
    super(0, 0, window_width, fitting_height(1))
    refresh
  end


The constructor is more or less the same as the one above, only it calls a method to get the window width and will only ever be the height of 1 line rather than taking an argument. It also calls its refresh method on creation.

def window_width
    return 160
  end


The window_width method determines the width of the window (you don't say?) and is hardcoded at 160. If you want longer/thinner gold windows, this is the value to aim your pistol at.

def refresh
    contents.clear
    draw_currency_value(value, currency_unit, 4, 0, contents.width - 8)
  end


The refresh method clears the contents (as with every window) and then calls draw_currency_value passing in value (which we'll look at next), currency_unit (which we'll look at after that, at x coordinate 4 and y coordinate 0, with a width of contents.width - 8. We looked at draw_currency_value in an earlier issue (it's a method of Window_Base if you forgot).

def value
    $game_party.gold
  end


This method is just a shorthand alternative to using $game_party.gold.

def currency_unit
    Vocab::currency_unit
  end


This method gets the currency unit as defined in Vocab, which gets its value from whatever you set in the database.

def open
    refresh
    super
  end


Last but not least, the method for opening the window calls refresh and then the open method of the parent class.

Window_MenuCommand
As the name may suggest, this is the command window shown in the menu. It has no public instance variables.

def self.init_command_position
    @@last_command_symbol = nil
  end


I don't know if we've covered class variables yet, but they're denoted by @@ as opposed to @ for an instance variable. init_command_position is a method of Window_MenuCommand itself rather than a given instance of it, and it sets the class variable @@last_command_symbol to nil. This means that the most recently-selected command in a menu command window can always be referenced.

def initialize
    super(0, 0)
    select_last
  end


The constructor simply calls the constructor of the parent (Window_Command) passing in 0 for both the X and Y coordinate, and calls select_last to change the selection to the most recent item that was highlighted in the window.

def window_width
    return 160
  end


The window_width method is hardcoded to return 160 pixels.

def visible_line_number
    item_max
  end


This method determines how many lines are visible in the window, and is returned as the maximum number of items the window can show.

def make_command_list
    add_main_commands
    add_formation_command
    add_original_commands
    add_save_command
    add_game_end_command
  end


The overwrite of the parent make_command_list method calls a number of methods to add commands to the menu; first we add the main commands, then the formation command, then any custom commands the developer has added, then the save command, and finally the game end command.

def add_main_commands
    add_command(Vocab::item,   :item,   main_commands_enabled)
    add_command(Vocab::skill,  :skill,  main_commands_enabled)
    add_command(Vocab::equip,  :equip,  main_commands_enabled)
    add_command(Vocab::status, :status, main_commands_enabled)
  end


The method for adding main commands calls add_command four times, one for each of the main commands for items, skills, equipment, and actor status. As you may or may not remember from last issue, the arguments being passed in here correspond to the parameters 'name', 'symbol' and 'enabled' respectively. The Vocab module references the terms set in the database. As with other symbol-based handlers, it really doesn't matter what name is used as long as the handler references the same symbol, but for the sake of clarity and ease of use the writers of the default scripts chose to use symbol names matching the command. main_commands_enabled is a method we'll see soon which determines whether the commands should be enabled or not.

def add_formation_command
    add_command(Vocab::formation, :formation, formation_enabled)
  end


This method adds the formation command using much the same syntax as the main commands did.

def add_original_commands
  end


This method adds original (custom) commands, and is blank by default. The intent is for script developers to alias this to add their own commands.

def add_save_command
    add_command(Vocab::save, :save, save_enabled)
  end


This method adds the save command, again using the standard command-adding syntax we've seen plenty of now.

def add_game_end_command
    add_command(Vocab::game_end, :game_end)
  end


The method for adding the game end command differs only in that it has no argument for 'enabled' due to the fact that it will always be available.

def main_commands_enabled
    $game_party.exists
  end


The method determining whether main commands are enabled returns the result of calling $game_party's exists method, which as you very likely won't remember from the issue that covered it returns whether its @actors instance variable is not empty. In other words, main commands are only enabled if there's at least 1 party member.

def formation_enabled
    $game_party.members.size >= 2 && !$game_system.formation_disabled
  end


This method determines whether the formation command is enabled, and returns the result of checking whether the size of the party is greater than or equal to 2 AND the formation_disabled flag of $game_system is false. The first condition means the formation command won't be available if you only have 1 party member, which makes sense as there'd be nobody to swap their position with.

def save_enabled
    !$game_system.save_disabled
  end


This method determines whether the save command is enabled, and simply returns whether the save_disabled property of $game_system is false.

def process_ok
    @@last_command_symbol = current_symbol
    super
  end


The overwrite of the Window_Selectable process_ok method sets the class variable @@last_command_symbol to the current symbol. This allows us to memorise the command that the player last had highlighted.

def select_last
    select_symbol(@@last_command_symbol)
  end


And its counterpart, this method selects the symbol contained in @@last_command_symbol.

Window_MenuStatus
This is the menu window which shows the status of party members i.e. their level, HP, MP and states. It has one public instance variable:

attr_reader   :pending_index            # Pending position (for formation)


As explained by the comment, pending_index is the pending position for use when changing the party formation (so that the command knows both the new and old positions).

def initialize(x, y)
    super(x, y, window_width, window_height)
    @pending_index = -1
    refresh
  end


Pretty standard fare as constructors go. Call the parent constructor passing in the x and y arguments along with the results of window_width and window_height, set the pending index to -1 (which basically just means "nothing") and call the refresh method.

def window_width
    Graphics.width - 160
  end


The window_width method returns the width of the window less 160 pixels.

def window_height
    Graphics.height
  end


window_height just returns the height of the window.

The savvy among you may realise that we're subtracting just as many pixels from window_width here as we returned in window_width for Window_MenuCommand. That's because the status window will be placed to the right of the command window, so we need to reduce the width by its width for it to fit on the screen correctly. You'll see this a lot with interconnected windows.

def item_max
    $game_party.members.size
  end


The overwrite of item_max for this window class returns the size of the members property of $game_party, or in other words, how many party members there are.

def item_height
    (height - standard_padding * 2) / 4
  end


The height of a single item is defined as (the height of the window less twice the standard padding) divided by 4. This will give us the height of a single "actor" in the status window, as it only shows 4 actors at a time.

def draw_item(index)
    actor = $game_party.members[index]
    enabled = $game_party.battle_members.include?(actor)
    rect = item_rect(index)
    draw_item_background(index)
    draw_actor_face(actor, rect.x + 1, rect.y + 1, enabled)
    draw_actor_simple_status(actor, rect.x + 108, rect.y + line_height / 2)
  end


The overwrite of draw_item, as with its parent, takes the index as a parameter. We set a temp variable called actor to the member of the party at index and enabled to whether the party's battle members include that actor.

Then we set a temp variable called rect to the result of calling item_rect passing in index (this gives us the rect required to display the given index on the window), draw the item background (which we'll look at in a sec), draw the actor's face, passing in the actor, rect's x + 1, rect's y + 1 and enabled, and then draw the simple status of the actor, passing in actor, the rect's x + 108, and the rect's y + half the height of a line.

So to put it in layman's terms, we'll colour in the background if the party member has been selected for a formation swap, draw their face very slightly to the right and down from the top left of the item rect (and slightly transparent if they're not in the battle party) and then draw their HP, MP etc. centred vertically in the rect.

def draw_item_background(index)
    if index == @pending_index
      contents.fill_rect(item_rect(index), pending_color)
    end
  end


This method fills in the rect for formation selections. If the passed-in index is equal to @pending_index, fill the item_rect for index with pending_color.

def process_ok
    super
    $game_party.menu_actor = $game_party.members[index]
  end


The overwrite of process_ok calls the parent method and then sets the menu_actor property of $game_party to the member corresponding to the currently-selected index.

def select_last
    select($game_party.menu_actor.index || 0)
  end


And to select the last, we call select passing in the index of $game_party's menu_actor property, or 0 if no value is returned.

def pending_index=(index)
    last_pending_index = @pending_index
    @pending_index = index
    redraw_item(@pending_index)
    redraw_item(last_pending_index)
  end


This method sets the pending index and takes index as a parameter. First, we set a temp variable called last_pending_index to the value of @pending_index, then set @pending_index to the newly passed-in index value, then redraw both the pending and last items (so that actor graphics/stats will be updated in both places).

Window_MenuActor
This window is used to select actors as targets of skills/items. No public instance variables this time. It inherits from Window_MenuStatus.

def initialize
    super(0, 0)
    self.visible = false
  end


Pretty standard constructor. We set its visible property to false so that it's invisible to begin with.

def process_ok
    $game_party.target_actor = $game_party.members[index] unless @cursor_all
    call_ok_handler
  end


This method processes OK key input. First, we set the target_actor property of $game_party to the member corresponding to the selected index unless the item targets the whole party, then we call the ok handler.

def select_last
    select($game_party.target_actor.index || 0)
  end


This overwrite of the select_last method is identical to the one for Window_MenuStatus only using target_actor instead of menu_actor.

def select_for_item(item)
    @cursor_fix = item.for_user?
    @cursor_all = item.for_all?
    if @cursor_fix
      select($game_party.menu_actor.index)
    elsif @cursor_all
      select(0)
    else
      select_last
    end
  end


This method sets the cursor position for item selection, taking the item as a parameter. First, we set @cursor_fix to the result of calling the for_user? method on the passed-in item, then set @cursor_all to the result of calling the for_all? method.

If @cursor_fix is true, we select the index of the party's menu actor (the user). Otherwise, if @cursor_all is true, we select index 0 (the party leader). Otherwise, we call select_last to select the most recently-targeted actor.

Window_ItemCategory
This window is for selecting item categories in the item menu or in shops. It inherits from Window_HorzCommand and has one public instance variable:

attr_reader   :item_window


There's no comment for it, but this is a property to hold the window object displaying the items that the category choice will be filtering.

def initialize
    super(0, 0)
  end


Nothing special in the constructor.

def window_width
    Graphics.width
  end


The width of the window is just the whole screen width.

def col_max
    return 4
  end


We're displaying 4 columns maximum. (I'm not actually sure why this is here because Window_HorzCommand already has an identical method. It's overwriting the parent method with one that's 100% identical to it. You could easily remove this and it would have no effect on the engine).

def update
    super
    @item_window.category = current_symbol if @item_window
  end


The frame update method calls its parent method, then sets the category property of @item_window to the current symbol if an item window is set.

def make_command_list
    add_command(Vocab::item,     :item)
    add_command(Vocab::weapon,   :weapon)
    add_command(Vocab::armor,    :armor)
    add_command(Vocab::key_item, :key_item)
  end


The overwrite for make_command_list adds 4 commands: one for items, one for weapons, one for armours, and one for key items, using the appropriate Vocab methods and symbols.

def item_window=(item_window)
    @item_window = item_window
    update
  end


This method sets the item window property, taking item_window as a parameter. Sets @item_window to the passed-in object, then calls update (which sets the item window category to the current symbol).

Window_ItemList
This is the window that displays the list of items on the item screen.

def initialize(x, y, width, height)
    super
    @category = :none
    @data = []
  end


The constructor does a couple of things besides calling the parent method. First, we set the instance variable @category to the symbol :none, and then set @data to an empty array.

def category=(category)
    return if @category == category
    @category = category
    refresh
    self.oy = 0
  end


This method sets the window's category, taking category as a parameter. We return if @category is already equal to the passed-in symbol, then set @category to category, refresh the window, and set its y origin to 0.

def col_max
    return 2
  end


The overwrite of col_max returns 2, as the item list will be 2 columns at most.

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


The overwrite of item_max returns the size of the @data array if it exists, and 1 otherwise.

def item
    @data && index >= 0 ? @data[index] : nil
  end


This method gets the current item: if @data exists and the index is greater than or equal to 0, we return the element of @data at index. Otherwise, we return nil.

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


This method determines whether the currently-selected item is enabled, by calling the enable? method on the element of @data at the current index.

def include?(item)
    case @category
    when :item
      item.is_a?(RPG::Item) && !item.key_item?
    when :weapon
      item.is_a?(RPG::Weapon)
    when :armor
      item.is_a?(RPG::Armor)
    when :key_item
      item.is_a?(RPG::Item) && item.key_item?
    else
      false
    end
  end


This method determines whether an item should be included in the list, taking the item as a parameter.

We have a case statement against @category:

When it's :item, we return true if the item is an instance of RPG::Item and not a key item, and false otherwise.

When it's :weapon, we return true if the item is an instance of RPG::Weapon, and false otherwise.

When it's :armor, we return true if the item is an instance of RPG::Armor, and false otherwise.

When it's :key_item, we return true if the item is an instance of RPG::Item and IS a key item, and false otherwise.

Otherwise, we return false.

def enable?(item)
    $game_party.usable?(item)
  end


This method determines whether to display an item as enabled (fully opaque) or disabled (partially transparent) and returns the value of calling the usable? method of $game_party for the passed-in item.

def make_item_list
    @data = $game_party.all_items.select {|item| include?(item) }
    @data.push(nil) if include?(nil)
  end


This method makes the list of items that will be displayed in the window. @data is set to the result of calling the select method on the result of calling the all_items method of $game_party using the iteration variable "item" and including the item in the returned array if the include? method returns true with the item as its argument.

...huh?

You may be a bit rusty with select as it's been a while since we've seen it, but basically you call it on an array passing in a block, and it returns a new array using the block as a filter condition to check whether the returned array should include the current element. In this case, all_items gets every item the party is carrying, and then we filter those items through the include? method, which essentially checks that the items are of the correct category. So only items will show for :item, only weapons will show for :weapon, etc.

Then, we push nil to @data if include? returns true when nil is passed in. Fun fact: This will NEVER happen. nil won't return true for any of the is_a? checks, so even if there is a category set it will return false in every scenario and subsequently this line is entirely pointless.

def select_last
    select(@data.index($game_party.last_item.object) || 0)
  end


The overwrite for select_last is a similarly-confusing-looking chain of method calls: we're calling select, passing in the element of @data with an index matching the object property of the last_item property of $game_party.

Okay, let's break this down. We know what select does: it takes the index of an item in the window that will be selected, so we know the method chain is ending up with an index somewhere.

@data contains a list of included items, so we know they're going to be item objects. Those aren't indexes, so we need to drill down further. The .index method of arrays returns the index of the first element of the calling array which matches the argument passed in, so what we need is an item object corresponding to the most recent one the party used. Luckily for us, $game_party has a last_item property, but it's an instance of Game_BaseItem and the elements of @data are all elements of $data_items/weapons/armors, so again we need to drill down a bit further. Again, luckily, Game_BaseItem has an object method which returns the element of $data_items/weapons/armors which corresponds to the item it represents. So combining all of this gives us a chain of method calls that will let us find the item in @data which matches the last item object the party used.

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


The method for drawing items in the window, as with the parent method it overwrites, takes index as a parameter. We set the temp variable item to the element of @data at index. Then, if item isn't nil, we set the temp variable rect to the result of calling item_rect with index as the argument (which gets the rect required to display a single item at the selected index location), reducing its width by 4 (because there's a tiny bit more of a margin on the left than there is on the right, this will make the item info more 'centred'). We then draw the item name, passing in the item object, rect's x, rect's y, and whether the item is enabled. Finally, we draw the party's held quantity of the item, passing in rect and the item object.

def draw_item_number(rect, item)
    draw_text(rect, sprintf(":%2d", $game_party.item_number(item)), 2)
  end


This method draws the quantity of an item held by the party, taking two parameters: rect and item. As we saw above, rect is the item rect for drawing the item at the current index, and item is the item object currently selected.

We call the draw_text method, which we looked at back when we covered Window_Base, passing in the rect, a call to sprintf, and 2 for "align right". It's all pretty straightforward except the sprintf, so let's look at that a bit more closely.

The sprintf method is intrinsic to Ruby and it's capable of way more stuff than we see here, but a more in-depth explanation of it is outside the scope of this series.

":%2d" means "a colon followed by an integer two characters wide". The second argument is what the % expression will be replaced with, which in this case is the number of the item that the party holds.

def update_help
    @help_window.set_item(item)
  end


This method updates the help window by calling its set_item method and passing in the selected item.

def refresh
    make_item_list
    create_contents
    draw_all_items
  end


The overwrite of Window_Selectable's refresh method is almost identical but adds a call to make_item_list to create the list of items for the window.

Window_SkillCommand
This window is used for selecting skills in the skill menu. It has one public instance variable:

attr_reader   :skill_window


This variable holds the window object containing the skill list. It's the equivalent of item_window from Window_ItemCategory.

def initialize(x, y)
    super(x, y)
    @actor = nil
  end


The constructor takes two parameters for x and y coordinates. First we do the usual call to super passing in the parameter values, then we set @actor to nil.

def window_width
    return 160
  end


The window_width method is hardcoded at 160 pixels.

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


This method sets the actor for the window, taking actor as a parameter. We return if @actor is already equal to the passed-in actor object. Otherwise, @actor is set to actor, then we refresh the window and select the most recently-selected actor.

def visible_line_number
    return 4
  end


The window will have 4 visible lines for skill types.

def make_command_list
    return unless @actor
    @actor.added_skill_types.sort.each do |stype_id|
      name = $data_system.skill_types[stype_id]
      add_command(name, :skill, true, stype_id)
    end
  end


To make the command list, first we return unless @actor contains a value, because otherwise there are no skills to make a list of. Then we take the actor's added skill types, sort them, and perform an each loop on the sorted list using the iteration variable "stype_id":

The temp variable "name" is assigned the skill type from $data_system at index stype_id, then we add a command to the list with the value of name, the symbol :skill, true for enabled, and an ext parameter of stype_id. This allows us to memorise the last skill type (can't use the symbol because they're all :skill).

def update
    super
    @skill_window.stype_id = current_ext if @skill_window
  end


Similarly to the update method of the item category window, this one calls the super method, then sets the stype_id of the skill window to current_ext if @skill_window contains data.

def skill_window=(skill_window)
    @skill_window = skill_window
    update
  end


This method sets the skill window associated with the command list, taking skill_window as a parameter. We set the @skill_window variable to the passed-in object, then call update.

def select_last
    skill = @actor.last_skill.object
    if skill
      select_ext(skill.stype_id)
    else
      select(0)
    end
  end


The overwrite for selecting the last item first sets a temp variable called "skill" to the current actor's last skill as an object (the element of $data_skills containing the skill data). If this variable contains a value, we call select_ext on the skill's stype ID, otherwise we call select passing in 0 (to select the first command in the list).

Window_SkillStatus
This window displays the user's status on the skill screen.

def initialize(x, y)
    super(x, y, window_width, fitting_height(4))
    @actor = nil
  end


The constructor is pretty standard, the only thing of note is that the height passed in to the super constructor is the fitting height for 4 lines. After calling super, @actor is set to nil.

def window_width
    Graphics.width - 160
  end


The width of the skill status window is the width of the game window less 160 pixels (the width of the command window).

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


This is pretty much the same as the previous actor= method; takes actor as a parameter, returns if @actor holds the same object, sets @actor to the passed-in actor, then refreshes the window.

def refresh
    contents.clear
    return unless @actor
    draw_actor_face(@actor, 0, 0)
    draw_actor_simple_status(@actor, 108, line_height / 2)
  end


The refresh method clears the contents and returns unless an actor is set. After this check, we draw the actor's face at (0, 0) and draw their simple status at an x coordinate of 108 and a y coordinate of half the height of a line.

Window_SkillList
This window displays the list of skills in the skill menu.

def initialize(x, y, width, height)
    super
    @actor = nil
    @stype_id = 0
    @data = []
  end


The constructor calls the super method, as normal, then sets @actor to nil, @stype_id to 0 and @data to . stype_id is obviously the skill type that will be shown, and data is an array of eligible skills.

def actor=(actor)
    return if @actor == actor
    @actor = actor
    refresh
    self.oy = 0
  end


Nothing special in the method for setting the actor variable, it's identical to category= from Window_ItemList.

def stype_id=(stype_id)
    return if @stype_id == stype_id
    @stype_id = stype_id
    refresh
    self.oy = 0
  end


...and this method is identical to the actor one only it's setting stype_id instead.

def col_max
    return 2
  end


col_max is hardcoded at 2, as we'll only have 2 columns of skills shown maximum.

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


The maximum number of items will either be the size of @data if it isn't nil, or 1 otherwise.

def item
    @data && index >= 0 ? @data[index] : nil
  end


This method gets the skill data at the highlighted index. If @data contains values AND index is greater than or equal to 0, we return the element of @data at index. Otherwise, we return nil.

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


Identical to the one from Window_ItemList.

def include?(item)
    item && item.stype_id == @stype_id
  end


Similar to the one from Window_ItemList but we only need to check two things: returns true if the passed-in item exists AND the item's skill type ID is the same as the @stype_id variable (in other words, whether the skill is of the type we currently have selected).

def enable?(item)
    @actor && @actor.usable?(item)
  end


The method for determining whether the item is enabled returns true if @actor contains a value AND the actor can use the item in question.

def make_item_list
    @data = @actor ? @actor.skills.select {|skill| include?(skill) } : []
  end


Pretty much the same as the one for Window_ItemList. If an actor exists, @data is set to the result of calling select on the actor's skills property, with the block using the iteration variable "skill" and including the element in the new array of the skill returns true when passed to include?. If an actor object does not exist, we return an empty array.

def select_last
    select(@data.index(@actor.last_skill.object) || 0)
  end


The only difference between this and the one from Window_ItemList is that it's using @actor.last_skill instead of $game_party.last_item.

def draw_item(index)
    skill = @data[index]
    if skill
      rect = item_rect(index)
      rect.width -= 4
      draw_item_name(skill, rect.x, rect.y, enable?(skill))
      draw_skill_cost(rect, skill)
    end
  end


This is almost identical to the one from Window_ItemList. The only differences are the temp variable name (skill instead of item) and it's calling draw_skill_cost instead of draw_item_number.

def draw_skill_cost(rect, skill)
    if @actor.skill_tp_cost(skill) > 0
      change_color(tp_cost_color, enable?(skill))
      draw_text(rect, @actor.skill_tp_cost(skill), 2)
    elsif @actor.skill_mp_cost(skill) > 0
      change_color(mp_cost_color, enable?(skill))
      draw_text(rect, @actor.skill_mp_cost(skill), 2)
    end
  end


Drawing a skill cost is a little more involved than drawing item quantities, but not by much:

If the selected actor's TP cost for the given skill is greater than 0, we change text colour to the tp_cost_color (slightly transparent if the skill is disabled) and then draw the TP cost in the rect aligned to the right.

Otherwise, if the actor's MP cost for the given skill is greater than 0, we change text colour to the mp_cost_color (again, slightly transparent if the skill is enabled) and then draw the MP cost in the rect aligned to the right.

def update_help
    @help_window.set_item(item)
  end


Updating the help simply calls the help window's set_item method passing in the selected item.

def refresh
    make_item_list
    create_contents
    draw_all_items
  end


The refresh method makes the item list, creates the window contents, and then draws all items. Simples!

And that's it for another Slip into Ruby! We still have a lot of windows to cover, but we'll get there eventually. And once we're done with windows, we can finally get to scenes! Excite!

As usual, comments are welcome. Until next time!