SLIP INTO RUBY - UNDER THE HOOD PART 23: SWORDS AND SHIELDS AND HELMETS, OH MY!
In which we get to grips with our equipment (that's what she said)
Trihan
- 06/16/2021 12:12 AM
- 1145 views
Hello, sports fans! Y'know, I address you like that every episode, but I don't even know if any of you like sports. I don't even really like sports, so I'm not sure why I do that. It's just a thing that I did in the beginning to be cute and it stuck, I guess. XD
But regardless of why I do it and no matter your likingness of sportsball, it's time once again for a seat-of-your-pants episode of
Today: Swords and Shields and Helmets, oh my!
Scene_Equip
Where would we be in an RPG without the equipment screen? This is where you augment your characters with all that shiny loot you pick up in dungeons and shops. It inherits from Scene_MenuBase.
Pretty standard start overwrite; we call the parent start method and then call methods to create a bunch of scene-specific windows: in this case the help window status window, command window, slot window and item window. It should hopefully be fairly obvious what all those are, but we'll be going over them in a sec anyway (except create_help_window since that is just pulled from the parent class and doesn't need to be redefined).
First up, the status window. We set instance variable @status_window to a new instance of Window_EquipStatus, passing in 0 as the x coordinate argument and @help_window's height as the y coordinate. This will place our status window on the far-left and just under the help window. We set @status_window's viewport to @viewport, and its actor to @actor.
A bit more going on with the command window. We set variable wx to @status_window's width, wy to @help_window's height and ww to the width of the game window minus @status_window's width, then assign to @command_window a new instance of Window_EquipCommand passing in wx as the x coordinate, wy as the y, and ww as the width. This gives us a window that's just to the right of the status window, just under the help window, and takes up the rest of the horizontal screen space. As before, we set its viewport to @viewport and then also set its help_window to @help_window.
The rest is just setting handlers: We'll be calling command_equip for the "Equip" command, command_optimize for the "Optimize" command, command_clear for "Clear", return_scene for hitting cancel, next_actor for page down, and prev_actor for page up.
Now we create the slot window. Set set wx to @Status_window's width, wy to @command_window's y plus its height, and ww to the width of the game window minus @status_window's width, then assign to @slot_window a new instance of Window_EquipSlot passing in wx, wy and ww for the x, y and width. This gives us a window that's just to the right of the status window, just below the command window and takes up the rest of the horizontal scren space.
We set @slot_window's viewport to @viewport, its help_window to @help_window and its status_window to @status_window, which establishes all the window links we need. We also set its actor to @actor and set handlers for the "ok" and "cancel" keys to call on_slot_ok and on_slot_cancel.
Creating the item window is very similar. We set wx to 0, wy to @slot_window's y plus its height, ww to the width of the game window, and wh to the game window height minus wy, then assign to @item_window a new instance of Window_EquipItem with wx as the x, wy as the y, ww as the width and wh as the height. This results in the item window being on the far-left, underneath the slot window, taking up the full width of the screen and the remaining vertical space.
Then we set @item_window's viewport to @viewport, its help_window to @help_window, its status_window to @status_window and its actor to @actor. We set the handlers for "ok" and "cancel" to on_item_ok and on_item_cancel, and finally set @slot_window's item_window to @item_window.
This method is called when the player selects the "Equip" command. Here we activate @slot_window and select index 0, which will select the first equipment slot (invariably weapon)
This method is called when the player selects the "Optimize" command. We call play_equip in the Sound module, then call optimize_equipments on @actor, and refresh @status_window, @slot_window and @command_window.
The method for clearing equipment is almost identical, except we call clear_equipments instead of optimize_equipments. Even the sound effect is the same.
When a slot is selected and the player hits ok, this method will be called. In this one, we just activate @item_window and select index 0, which will select the first item of equipment in inventory which is applicable to the selected slot.
And if we hit cancel, we unselect @slow_window and activate @command_window.
Hitting ok on a piece of equipment will call this method. Again we call play_equip in the Sound module, then we call @actor's change_equip method, passing in @slot_window's index as the slot ID, and @item_window's item as the item. Then we activate and refresh @slot_window, and unselect and refresh @item_window, which will take us back to the slot selection and update the display to reflect the equipment change.
Hitting cancel in the item selection will activate @slow_window and unselect @item_window. Nothing exciting here really.
When the player uses page up/page down to change actors, we will set the actor property of @status_window, @slot_window and @item_window to @actor, and then activate @command_window. This ensures that no matter which window was active at the time, the new actor will begin on the command window.
Scene_Status
This is the scene which displays the actor's full status. It inherits from Scene_MenuBase, and may be the shortest class we've seen yet. Don't believe me? Well...
So start. We call the parent method, as we do, and set @status_window to a new instance of Window_Status, passing in @actor as the actor. We set the "cancel" handler to return_scene, and the "pagedown"/"pageup" handlers to next_actor and prev_actor.
When the actor is changed, we set @status_window's actor to @actor and then activate it.
And that is literally it for the status scene. Told you!
Scene_File
For the start method, first we call the parent method, then call a few viewport/vindow creation methods (create_help_window, create_savefile_viewport and create_savefile_windows), and then we call init_selection.
In the terminate method we call the parent method, then dispose of @savefile_viewport, and then run an each loop through @savefile_windows using iteration variable "window" and in the block we dispose of the window. This will ensure that all memory used up by these windows is freed.
In our overwrite to the update method, first we call the parent method (as is tradition). Then as before run an each loop through @savefile_windows. Instead of dispose, this time we're calling update. And then after that loop, we call update_savefile_selection.
Here we create the help window. We set @help_window to a new instance of Window_Help, passing in 1 as the line_number argument. Then we call its set_text method and pass in the result of help_window_text.
...which in this class is empty, because we're never going to directly call Scene_File. It's a superclass for Scene_Save and Scene_Load, which will implement their own overwrites.
To create the save file viewport, first we set @savefile_viewport to a new Viewport instance. Then we set its rect's y coordinate to the height of @help_window, and subtract that height from the height of the rect. This will give us a viewport for save files which covers the entire screen space not taken up by the help window.
In this method we create the save file windows, as the name suggests. We set @savefile_windows to a new Array instance with item_max items in it (we'll see that method shortly). and pass that array a "do" block where each element (i) is assigned a new instance of Window_SaveFile, passing in savefile_height as the height argument and i as the index. Following that block, we run an each loop on @savefile_windows where the block sets the viewport of each element (window) to @savefile_viewport, thus keeping all created windows in the same viewport we just created.
Now we finally come to init_selection. We set @index to first_savefile_index, which again we'll see shortly. We set the "selected" property of the element in @savefile_windows with an index of @index to true to show that we've selected it, then set self's top_index to @index minus visible_max divided by 2. Finally, we call ensure_cursor_visible. I'll explain that top_index bit a little more when we come to Scene_Save.
This method determines the maximum number of save files the game can have. We return savefile_max from DataManager, which by default returns 16.
This one, on the other hand, determines the number of save files visible on screen at any one time. And here, we return 4.
This method determines the height of each save file in the viewport. We return the height of @savefile_viewport's rect divided by visible_max.
This method gets the first save file index, and we quite simply return 0. Nothing more to it.
Similarly, the index method returns @index. I'm not going to insult your intelligence by elaborating further.
top_index gets the index of the entry which should be at the top of the screen, and for that we return the origin Y of @savefile_viewport divided by its height.
We also have a setter for top_index, taking index as a parameter. We set index to 0 if it's less than that, then if index is greater than item_max minus visible_max, we set it to item_max minus visible_max. Finally, we set @savefile_viewport's origin Y to index multplied by savefile_height.
So let's say index is 14. It's not less than 0 so the first line doesn't affect it Then 14 is greater than 16 - 4, so we set it to 16 - 4, which makes it 12. Then we set the viewport's oy to 12 * (368 / 4) which gives us 1104. Therefore, the origin Y will be 1104 rather than 0. Basically what this means is that the top row of the viewport's display will start at an offset of 1104 pixels from what it usually would, resulting in the view "shifting" to accommodate the index position.
This method gets the bottom index, and we take that as top_index plus visible_max - 1. So if top_index is 3, bottom_index will be 3 + 4 - 1 or 6. Therefore, save #7 will be at the bottom.
And we also have a setter for bottom_index, taking index as a parameter. What we're actually setting though is self's top_index, and we set it to index minus (visible_max - 1). So if we're setting it to 6, we'll be setting top_index to 6 - (4 - 1), which results in 3. And we can follow that to check that it matches up; if top_index is 3, bottom_index is nw 3 + 4 - 1, which gives us 6. Simples!
This method is called during the scene's update cycle. First, we return on_savefile_ok if the player is triggering input :C, which is usually enter. We then return on_savefile_cancel if they're triggering :B (usually esc), and then we call update_cursor.
The base implementation of the on_savefile_ok method doesn't do anything, but we have it here ready to be overwritten.
The on_savefile_cancel method, on the other hand, calls play_cancel from the Sound module and then calls return_scene, which will, by default, return the player to either the title screen or the menu.
We need an update_cursor in the scene because if you'll remember we overwrote it in Window_Savefile to not affect indexes any more. And we need to do that because each save file uses its own window, so we need to handle indexes slightly differently.
So we set last_index to @index. Then we call methods depending on input. If the player presses DOWN, we call cursor_down, and if they press UP we call cursor_up (passing in whether the player is *triggering* down or up, which is what stops the file list from wrapping if you're holding the key and hit the bottom/top). We call cursor_pagedown if they hit :R and cursor_pageup if they hit :L. Then if @index is not equal to last_index (meaning it was changed by one of the method calls), we call play_cursor in the Sound module and swap the selected property between the last_index and @index index of @savefile_windows (meaning we deselect the old window and reselect the new one).
This method handles the player hitting the down arrow key. If @index is less than item_max minus 1 or wrap is true, we set @index to (@index plus 1) mod item_max. Then we call ensure_cursor_visible.
So if @index is 14, it's less than 16 - 1, so we set it to (14 + 1) % 4, which gives us 15. Conversely, if @index is 15, it isn't less than 15 - 1, so to run this line wrap needs to be true (which it will be if the player only pressed down rather than holding it) and in this case @index will now be (15 + 1) % 16 which gives us 0 and wraps it back to the top.
This one handles the up arrow key, and does pretty much the opposite of cursor_down. This time though, the check we're making is @index greater than 0 or wrap, and the index setting is (@index minus 1 plus item_max) mod item_max. So say the index is 0 and the player just hit up, wrap is true so we set @index to (0 - 1 + 16) % 16 which gives us 15 and loops around to the bottom. This is a common method of doing wrapping selection windows.
For pagedown, we're jumping multiple windows at a time. So first, we check whether top_index plus visible_max is less than item_max. If so, we add visible_max to self's top_index, then set @index to the minimum value between @index plus visible_max and item_max minus 1.
So for example, if our top index is currently 3 and @index is 5, 3 + 4 is less than 16, so the if statement runs. We add 4 to top_index so now it's 7, then set @index to the minimum between 5 + 4 and 15. 9 is less than 15, so we set it to 9.
And for pageup, we check whether top_index is greater than 0. If so, we subtract visible_max from self's top_index, and set @index to the maximum value between @index minus visible_max and 0.
So for example, if top_index returns 1, we set top_index to 1 - 4, but then the setter will set it to 0 since it's less than 0. @index is then set to the maximum of 0 - 4 and 0, which obviously returns 0.
This method, well, ensures cursor visibility. So if index is less than top_index, we set self's top_index to index. And if index is greater than bottom_index, we set self's bottom_index to index. This just makes sure that if our index changes to one that isn't visible, it becomes visible afterwards.
That's it for the base scene! Now let's take a look at its kids. They're fairly light functionality-wise since the base class does most of the heavy lifting already.
Scene_Save
So here we have the scene for saving one's game. It inherits, obviously, from Scene_File.
Our first port of call is to overwrite help_window_text to return Vocab::SaveMessage. If you take a quick gander back at Vocab, you'll see that SaveMessage returns "Save to which file?".
This method overwrites the one that returns 0, and instead we return the last_savefile_index property in DataManager, which will be the ID of the most recently accessed file.
This is the method that's called when the player hits ok on a save file. For starters, we call the parent method. Then if DataManager's save_game method returns true with @index passed to it, we call on_save_success. Otherwise, we call play_buzzer in the Sound module.
Finally we have the method called when the player successfully saves a game. We call the Sound module's play_save method, then call return_scene.
And that's it! That's all that's needed for creating your save games. Crazy, huh?
Scene_Load
Scene_Load is actually almost identical. There's just a couple of differences in method names and on_load_success does a couple of extra things.
So this method returns LoadMessage rather than SaveMessage. Nothing complicated.
This one looks similar to the one in Scene_Save but actually isn't, since we're calling latest_savefile_index rather than last_savefile_index. The difference here is that latest returns the most recent save file by timestamp, rather than the last one *accessed*.
The only differences here are that we're calling load_game instead of save_game, and on_load_success instead of on_save_success.
And finally, when a load is successful we play_load from Sound call fadeout_all (which fades the screen out), call on_after_load in $game_system (which sets the frame count and plays BGM/BGS) and finally we call SceneManager's goto method passing in Scene_Map, which...well, takes the player to the map scene.
Scene_End
This scene is the one the player goes to when they choose "Game End" in the menu. It inherits from Scene_MenuBase.
In our start method, first we call the parent method, then we call create_command_window.
And in pre_terminate we call the parent method and then close_command_window.
This overwrite of create_background doesn't do much. First we call the parent method and then we set the tone of @background_sprite to 0 R, 0 G, 0 B and 128 grey, which effectively just makes the background darker.
In create_command_window, first we set @command_window to a new instance of Window_GameEnd. Then we set the handlers for :to_title, :shutdown and :cancel to command_to_title, command_shutdown and return_scene respectively.
This method is called from pre_terminate. First we call close on @command_window, and then call update until its close? method returns true. In other words, do nothing but perform frame updates until the window is fully closed.
This method handles the "To Title" command. We call close_command_window, then fadeout_all to fade the screen, and finally SceneManager's goto method, passing in Scene_Title, to return the player to the title screen with no scene stack.
This method handles the "Shut down" command. Again, we call close_command_window and fadeout_all, but this time we call SceneManager.exit, which closes the game completely.
And that's it for Scene_End! We're fair flying through them today.
Scene_Shop
Another scene we'd be lost without. Without this one, the player would never be able to stock up on healing items, or buy a sweet new axe, or sell all those monster parts the party inexplicably picks up and carries around. The shop scene inherits from Scene_MenuBase.
The prepare method is called just after the shop scene is called in the shop processing event command, and takes as its parameters "goods", an array of the items the shop sells, and "purchase_only", a true/false flag denoting whether the player can only buy from the shop or can buy and sell. And in this method, we set @goods to goods and @purchase_only to purchase_only.
So this brings us full circle to be able to explain where the goods that go into Window_ShopBuy come from.
Each element of the array is itself an array containing the item type (0 for item, 1 for weapons, 2 for armour), item ID, whether the price is standard or custom (0 for standard, 1 for custom), and the price (which is only used if using custom). In addition, the first element will have a 5th parameter for purchase_only: true if the player can only buy, or false if they can buy and sell. For example, taking the default database, for a shop that sells standard Potion, Hi-Potion for 5g, standard Hand Ax, Gigantes Axe for half price, standard Casual Clothes and Mithril Plate for 5g, the array would be
[, , , , , ]
In start, all we're doing is calling the parent method and then a bunch of window creation methods: for the help window, gold window, command window, dummy window, number window, status window buy window, category window and sell window. The only one that might not be self-explanatory is the dummy window, which I'll go over when we come to it.
This method creates the gold window. We set @gold_window to a new instance of Window_Gold. Then we set that window's viewport to @viewport, its x coordinate to the width of the game window minus the width of the gold window, and the y coordinate to the height of the help window. This will position the gold display at the far right of the screen underneath the help window.
For the command window, we set @command_window to a new instance of Window_ShopCommand passing in @gold_window's x coordinate as the width and @purchase_only as purchase only. Then we set its viewport to @viewport and its y coordinate to the height of @help_window. This places the command window in the remaining horizontal space to the left of the gold window.
We then set the handlers for :buy, :sell and :cancel to command_buy, command_sell and return_scene respectively.
This method creates the dummy window. First we set wy to @command-window's y plus its height, then wh to the game window height minus wy. Then we set @dummy_window to a new instance of Window_Base, passing in 0 as the x, wy as the y, the width of the game window as the width and wh as the height. This gives us a window on the far left of the screen, underneath the command window and taking up the remaining vertical space.
But what is the dummy window? It's just the big window that appears when the shop first opens, and exists only to fill up the space in an aesthetically-pleasing way until a command is chosen and there's other stuff to display.
Now we create the number window. We set wy to @dummy_window's y coordinate, and wh to its height. Then we set @number_window to a new instance of Window_ShopNumber passing in 0 as the x, wy as the y, and wh as the height. This gives us a window on the far left of the screen, at the same y position as the dummy window and the same height as it. It won't be quite as wide, though.
We set @number_window's viewport to @viewport and call hide on it so that it won't be visible when the scene first opens. Then we set its :ok handler to on_number_ok, and its :cancel handler to on_number_cancel.
On to the status window. We set wx to the width of @number_window, wy to the y coordinate of @dummy_window, ww to the game window width minus wx, and wh to @dummy_window's height. Then we set @status_Window to a new instance of Window_ShopStatus passing in wx, wy, ww and wh as the x, y, width and height. This places the status window to the right of the number window taking up the rest of the horizontal space, at the same y coordinate as the dummy window and the same height. After that, we set @status_window's viewport to @viewport and hide it so that it isn't visible when the scene opens.
This method creates the buy window. We set wy to @dummy_window's y, and wh to its height. Then we set @buy_window to a new instance of Window_ShopBuy passing in 0 as the x, wy as the y, wh as the height and @goods as the shop goods. This will result in the buy window being the same size and position as the number window.
Then we set @buy_window's viewport to @viewport, its help_window to @help_window and its status_window to @status_window, before hiding it. Then we set the :ok handler to on_buy_ok and the :cancel handler to on_buy_cancel.
This method creates the category window. We set @category_window to a new instance of Window_ItemCategory, then set its viewport to @viewport, its help_window to @help_window, its y coordinate to @dummy_window's y, and then we hide and deactivate it (we need to do both because otherwise, since it's a selectable window, it will be active when the scene begins). Finally, we set the handler for :ok to on_category_ok, and the one for :cancel to on_category_cancel.
Then we create the sell window. We set wy to @category_window's y plus its height, then wh to the game window height - wy, and set @sell_window to a new instance of Window_ShopSell, passing in 0 for the x, wy for the y, the width of the game window as the width and wh as the height. This will give us a window underneath the category window which takes up all remaining screen space. Then we set its viewport to @viewport and its help_window to @help_window before hiding it. We set the :ok handler to on_sell_ok and the :cancel handler to on_sell_cancel, and finally we set @category_window's item_window to @sell_window.
This method activates the buy window. We set @buy_window's money property to the result of the money method, which is coming up in a bit. We show and activate @buy_window, and then show @status_window.
This method activates the sell window. Here we show @category_window, refresh, show and activate @sell_window, and hide @status_window.
This method is called when the "Buy" command is selected. All we're doing here is hiding @dummy_window and calling activate_buy_window.
This method is called when the "Sell" command is selected. We hide @dummy_window, show and activate @category_window, then show, unselect and refresh @sell_window.
This method is the handler for pressing :ok in the buy window. We set @item to @buy_window's item and hide @buy_window, then call @number_window's set method passing in @item for item, max_buy for max, buying_price for price and currency_unit for currency_unit, then show and activate @number_window.
The handler for :cancel in the buy window. We activate @command_window and show @dummyWindow, then hide @buy_window and @status_window, set @status_window's item to nil, and clear @help_window.
This method handles hitting :ok in the category window. All we're doing is calling activate_sell_window and selecting index 0 in @sell_window.
This method handles hitting :cancel in the category window. We activate @command_window, show @dummy_window and then hide @category_window and @sell_window.
This method handles hitting :ok in the sell window. We set @item to @sell_window's item and then set @status_window's item to @item. Then we hide the @category_window and sell_window. We call @number_window's set method passing in @item as item, max_sell as max, selling_price as price and currency_unit as currency_unit, and finally we show and activate @number_window and show @status_window.
When hitting cancel in the sell window, we unselect @sell_window and activate @category_window. Then we set @status_window's item to nil and clear @help_window.
This method handles hitting ok in the number window. We call play_shop in the Sound module, and then have a case (switch) statement on @command_window's current_symbol: if it's :buy, we call do_buy. If it's :sell, we call do_sell. In both cases we pass in @number_window's number value as the argument. After the case statement, we call end_number_input, then refresh the @gold_window and @status_window.
This method is called when hitting cancel in the number window. We call play_cancel in the Sound module and then end_number_input.
This method processes final confirmation of buying an item (via hitting ok in the number window) and takes number as a parameter. We call $game_party's lose_gold method passing in number multiplied by buying_price, and its gain_item method passing in @item as the item and number as the quantity. This will remove X gold for each of the item bought, and add that number to the party's inventory.
do_sell is effectively the opposite. We're calling gain_gold instead of lose_gold and lose_item instead of gain_item.
This method ends the number input. We hide @number_window, and run a case statement against @command_window's current_symbol. If it's :buy, we call activate_buy_window, and if it's :sell we call activate_sell_window.
This method determines the maximum number of the selected item which the party is ablve to buy.
We set max to $game_party's max_item_number passing in @item, minus its item_number passing in the same number. This gives the difference between how many of the item the party has and how many it can have. Then, if buying_price is 0, we return max, and if it isn't we return the minimum value between max and money divided by buying_price (which will give us the number the player can afford if that's less than the maximum they can hold).
max_sell is a bit easier: we just return $game_party's item_number method passing in @item, because it stands to reason that the maximum number of a thing you can sell is the number of it that you have.
The money method is just a wrapped for @gold_window's value. It's shorthand for how much gold the party has, basically.
This method just returns @gold_window's currency_unit, which gets the currency unit from Vocab.
This method gets the buying price for the selected item. Here, we're just returning the value from @buy_window's price method when @item is passed to it.
selling_price, on the other hand, returns half of @item's price.
And that's all there is to it!
We'll cut off there for this episode. I think I might be able to cover all the remaining classes next week, but it may require two episodes just because of how long Scene_Battle is. We'll see when we get there, I suppose.
Until next time!
But regardless of why I do it and no matter your likingness of sportsball, it's time once again for a seat-of-your-pants episode of

Today: Swords and Shields and Helmets, oh my!
Scene_Equip
Where would we be in an RPG without the equipment screen? This is where you augment your characters with all that shiny loot you pick up in dungeons and shops. It inherits from Scene_MenuBase.
def start super create_help_window create_status_window create_command_window create_slot_window create_item_window end
Pretty standard start overwrite; we call the parent start method and then call methods to create a bunch of scene-specific windows: in this case the help window status window, command window, slot window and item window. It should hopefully be fairly obvious what all those are, but we'll be going over them in a sec anyway (except create_help_window since that is just pulled from the parent class and doesn't need to be redefined).
def create_status_window @status_window = Window_EquipStatus.new(0, @help_window.height) @status_window.viewport = @viewport @status_window.actor = @actor end
First up, the status window. We set instance variable @status_window to a new instance of Window_EquipStatus, passing in 0 as the x coordinate argument and @help_window's height as the y coordinate. This will place our status window on the far-left and just under the help window. We set @status_window's viewport to @viewport, and its actor to @actor.
def create_command_window wx = @status_window.width wy = @help_window.height ww = Graphics.width - @status_window.width @command_window = Window_EquipCommand.new(wx, wy, ww) @command_window.viewport = @viewport @command_window.help_window = @help_window @command_window.set_handler(:equip, method(:command_equip)) @command_window.set_handler(:optimize, method(:command_optimize)) @command_window.set_handler(:clear, method(:command_clear)) @command_window.set_handler(:cancel, method(:return_scene)) @command_window.set_handler(:pagedown, method(:next_actor)) @command_window.set_handler(:pageup, method(:prev_actor)) end
A bit more going on with the command window. We set variable wx to @status_window's width, wy to @help_window's height and ww to the width of the game window minus @status_window's width, then assign to @command_window a new instance of Window_EquipCommand passing in wx as the x coordinate, wy as the y, and ww as the width. This gives us a window that's just to the right of the status window, just under the help window, and takes up the rest of the horizontal screen space. As before, we set its viewport to @viewport and then also set its help_window to @help_window.
The rest is just setting handlers: We'll be calling command_equip for the "Equip" command, command_optimize for the "Optimize" command, command_clear for "Clear", return_scene for hitting cancel, next_actor for page down, and prev_actor for page up.
def create_slot_window wx = @status_window.width wy = @command_window.y + @command_window.height ww = Graphics.width - @status_window.width @slot_window = Window_EquipSlot.new(wx, wy, ww) @slot_window.viewport = @viewport @slot_window.help_window = @help_window @slot_window.status_window = @status_window @slot_window.actor = @actor @slot_window.set_handler(:ok, method(:on_slot_ok)) @slot_window.set_handler(:cancel, method(:on_slot_cancel)) end
Now we create the slot window. Set set wx to @Status_window's width, wy to @command_window's y plus its height, and ww to the width of the game window minus @status_window's width, then assign to @slot_window a new instance of Window_EquipSlot passing in wx, wy and ww for the x, y and width. This gives us a window that's just to the right of the status window, just below the command window and takes up the rest of the horizontal scren space.
We set @slot_window's viewport to @viewport, its help_window to @help_window and its status_window to @status_window, which establishes all the window links we need. We also set its actor to @actor and set handlers for the "ok" and "cancel" keys to call on_slot_ok and on_slot_cancel.
def create_item_window wx = 0 wy = @slot_window.y + @slot_window.height ww = Graphics.width wh = Graphics.height - wy @item_window = Window_EquipItem.new(wx, wy, ww, wh) @item_window.viewport = @viewport @item_window.help_window = @help_window @item_window.status_window = @status_window @item_window.actor = @actor @item_window.set_handler(:ok, method(:on_item_ok)) @item_window.set_handler(:cancel, method(:on_item_cancel)) @slot_window.item_window = @item_window end
Creating the item window is very similar. We set wx to 0, wy to @slot_window's y plus its height, ww to the width of the game window, and wh to the game window height minus wy, then assign to @item_window a new instance of Window_EquipItem with wx as the x, wy as the y, ww as the width and wh as the height. This results in the item window being on the far-left, underneath the slot window, taking up the full width of the screen and the remaining vertical space.
Then we set @item_window's viewport to @viewport, its help_window to @help_window, its status_window to @status_window and its actor to @actor. We set the handlers for "ok" and "cancel" to on_item_ok and on_item_cancel, and finally set @slot_window's item_window to @item_window.
def command_equip @slot_window.activate @slot_window.select(0) end
This method is called when the player selects the "Equip" command. Here we activate @slot_window and select index 0, which will select the first equipment slot (invariably weapon)
def command_optimize Sound.play_equip @actor.optimize_equipments @status_window.refresh @slot_window.refresh @command_window.activate end
This method is called when the player selects the "Optimize" command. We call play_equip in the Sound module, then call optimize_equipments on @actor, and refresh @status_window, @slot_window and @command_window.
def command_clear Sound.play_equip @actor.clear_equipments @status_window.refresh @slot_window.refresh @command_window.activate end
The method for clearing equipment is almost identical, except we call clear_equipments instead of optimize_equipments. Even the sound effect is the same.
def on_slot_ok @item_window.activate @item_window.select(0) end
When a slot is selected and the player hits ok, this method will be called. In this one, we just activate @item_window and select index 0, which will select the first item of equipment in inventory which is applicable to the selected slot.
def on_slot_cancel @slot_window.unselect @command_window.activate end
And if we hit cancel, we unselect @slow_window and activate @command_window.
def on_item_ok Sound.play_equip @actor.change_equip(@slot_window.index, @item_window.item) @slot_window.activate @slot_window.refresh @item_window.unselect @item_window.refresh end
Hitting ok on a piece of equipment will call this method. Again we call play_equip in the Sound module, then we call @actor's change_equip method, passing in @slot_window's index as the slot ID, and @item_window's item as the item. Then we activate and refresh @slot_window, and unselect and refresh @item_window, which will take us back to the slot selection and update the display to reflect the equipment change.
def on_item_cancel @slot_window.activate @item_window.unselect end
Hitting cancel in the item selection will activate @slow_window and unselect @item_window. Nothing exciting here really.
def on_actor_change @status_window.actor = @actor @slot_window.actor = @actor @item_window.actor = @actor @command_window.activate end
When the player uses page up/page down to change actors, we will set the actor property of @status_window, @slot_window and @item_window to @actor, and then activate @command_window. This ensures that no matter which window was active at the time, the new actor will begin on the command window.
Scene_Status
This is the scene which displays the actor's full status. It inherits from Scene_MenuBase, and may be the shortest class we've seen yet. Don't believe me? Well...
def start super @status_window = Window_Status.new(@actor) @status_window.set_handler(:cancel, method(:return_scene)) @status_window.set_handler(:pagedown, method(:next_actor)) @status_window.set_handler(:pageup, method(:prev_actor)) end
So start. We call the parent method, as we do, and set @status_window to a new instance of Window_Status, passing in @actor as the actor. We set the "cancel" handler to return_scene, and the "pagedown"/"pageup" handlers to next_actor and prev_actor.
def on_actor_change @status_window.actor = @actor @status_window.activate end
When the actor is changed, we set @status_window's actor to @actor and then activate it.
And that is literally it for the status scene. Told you!
Scene_File
def start super create_help_window create_savefile_viewport create_savefile_windows init_selection end
For the start method, first we call the parent method, then call a few viewport/vindow creation methods (create_help_window, create_savefile_viewport and create_savefile_windows), and then we call init_selection.
def terminate super @savefile_viewport.dispose @savefile_windows.each {|window| window.dispose } end
In the terminate method we call the parent method, then dispose of @savefile_viewport, and then run an each loop through @savefile_windows using iteration variable "window" and in the block we dispose of the window. This will ensure that all memory used up by these windows is freed.
def update super @savefile_windows.each {|window| window.update } update_savefile_selection end
In our overwrite to the update method, first we call the parent method (as is tradition). Then as before run an each loop through @savefile_windows. Instead of dispose, this time we're calling update. And then after that loop, we call update_savefile_selection.
def create_help_window @help_window = Window_Help.new(1) @help_window.set_text(help_window_text) end
Here we create the help window. We set @help_window to a new instance of Window_Help, passing in 1 as the line_number argument. Then we call its set_text method and pass in the result of help_window_text.
def help_window_text return "" end
...which in this class is empty, because we're never going to directly call Scene_File. It's a superclass for Scene_Save and Scene_Load, which will implement their own overwrites.
def create_savefile_viewport @savefile_viewport = Viewport.new @savefile_viewport.rect.y = @help_window.height @savefile_viewport.rect.height -= @help_window.height end
To create the save file viewport, first we set @savefile_viewport to a new Viewport instance. Then we set its rect's y coordinate to the height of @help_window, and subtract that height from the height of the rect. This will give us a viewport for save files which covers the entire screen space not taken up by the help window.
def create_savefile_windows @savefile_windows = Array.new(item_max) do |i| Window_SaveFile.new(savefile_height, i) end @savefile_windows.each {|window| window.viewport = @savefile_viewport } end
In this method we create the save file windows, as the name suggests. We set @savefile_windows to a new Array instance with item_max items in it (we'll see that method shortly). and pass that array a "do" block where each element (i) is assigned a new instance of Window_SaveFile, passing in savefile_height as the height argument and i as the index. Following that block, we run an each loop on @savefile_windows where the block sets the viewport of each element (window) to @savefile_viewport, thus keeping all created windows in the same viewport we just created.
def init_selection @index = first_savefile_index @savefile_windows[@index].selected = true self.top_index = @index - visible_max / 2 ensure_cursor_visible end
Now we finally come to init_selection. We set @index to first_savefile_index, which again we'll see shortly. We set the "selected" property of the element in @savefile_windows with an index of @index to true to show that we've selected it, then set self's top_index to @index minus visible_max divided by 2. Finally, we call ensure_cursor_visible. I'll explain that top_index bit a little more when we come to Scene_Save.
def item_max DataManager.savefile_max end
This method determines the maximum number of save files the game can have. We return savefile_max from DataManager, which by default returns 16.
def visible_max return 4 end
This one, on the other hand, determines the number of save files visible on screen at any one time. And here, we return 4.
def savefile_height @savefile_viewport.rect.height / visible_max end
This method determines the height of each save file in the viewport. We return the height of @savefile_viewport's rect divided by visible_max.
def first_savefile_index return 0 end
This method gets the first save file index, and we quite simply return 0. Nothing more to it.
def index @index end
Similarly, the index method returns @index. I'm not going to insult your intelligence by elaborating further.
def top_index @savefile_viewport.oy / savefile_height end
top_index gets the index of the entry which should be at the top of the screen, and for that we return the origin Y of @savefile_viewport divided by its height.
def top_index=(index) index = 0 if index < 0 index = item_max - visible_max if index > item_max - visible_max @savefile_viewport.oy = index * savefile_height end
We also have a setter for top_index, taking index as a parameter. We set index to 0 if it's less than that, then if index is greater than item_max minus visible_max, we set it to item_max minus visible_max. Finally, we set @savefile_viewport's origin Y to index multplied by savefile_height.
So let's say index is 14. It's not less than 0 so the first line doesn't affect it Then 14 is greater than 16 - 4, so we set it to 16 - 4, which makes it 12. Then we set the viewport's oy to 12 * (368 / 4) which gives us 1104. Therefore, the origin Y will be 1104 rather than 0. Basically what this means is that the top row of the viewport's display will start at an offset of 1104 pixels from what it usually would, resulting in the view "shifting" to accommodate the index position.
def bottom_index top_index + visible_max - 1 end
This method gets the bottom index, and we take that as top_index plus visible_max - 1. So if top_index is 3, bottom_index will be 3 + 4 - 1 or 6. Therefore, save #7 will be at the bottom.
def bottom_index=(index) self.top_index = index - (visible_max - 1) end
And we also have a setter for bottom_index, taking index as a parameter. What we're actually setting though is self's top_index, and we set it to index minus (visible_max - 1). So if we're setting it to 6, we'll be setting top_index to 6 - (4 - 1), which results in 3. And we can follow that to check that it matches up; if top_index is 3, bottom_index is nw 3 + 4 - 1, which gives us 6. Simples!
def update_savefile_selection return on_savefile_ok if Input.trigger?(:C) return on_savefile_cancel if Input.trigger?(:B) update_cursor end
This method is called during the scene's update cycle. First, we return on_savefile_ok if the player is triggering input :C, which is usually enter. We then return on_savefile_cancel if they're triggering :B (usually esc), and then we call update_cursor.
def on_savefile_ok end
The base implementation of the on_savefile_ok method doesn't do anything, but we have it here ready to be overwritten.
def on_savefile_cancel Sound.play_cancel return_scene end
The on_savefile_cancel method, on the other hand, calls play_cancel from the Sound module and then calls return_scene, which will, by default, return the player to either the title screen or the menu.
def update_cursor last_index = @index cursor_down (Input.trigger?(:DOWN)) if Input.repeat?(:DOWN) cursor_up (Input.trigger?(:UP)) if Input.repeat?(:UP) cursor_pagedown if Input.trigger?(:R) cursor_pageup if Input.trigger?(:L) if @index != last_index Sound.play_cursor @savefile_windows[last_index].selected = false @savefile_windows[@index].selected = true end end
We need an update_cursor in the scene because if you'll remember we overwrote it in Window_Savefile to not affect indexes any more. And we need to do that because each save file uses its own window, so we need to handle indexes slightly differently.
So we set last_index to @index. Then we call methods depending on input. If the player presses DOWN, we call cursor_down, and if they press UP we call cursor_up (passing in whether the player is *triggering* down or up, which is what stops the file list from wrapping if you're holding the key and hit the bottom/top). We call cursor_pagedown if they hit :R and cursor_pageup if they hit :L. Then if @index is not equal to last_index (meaning it was changed by one of the method calls), we call play_cursor in the Sound module and swap the selected property between the last_index and @index index of @savefile_windows (meaning we deselect the old window and reselect the new one).
def cursor_down(wrap) @index = (@index + 1) % item_max if @index < item_max - 1 || wrap ensure_cursor_visible end
This method handles the player hitting the down arrow key. If @index is less than item_max minus 1 or wrap is true, we set @index to (@index plus 1) mod item_max. Then we call ensure_cursor_visible.
So if @index is 14, it's less than 16 - 1, so we set it to (14 + 1) % 4, which gives us 15. Conversely, if @index is 15, it isn't less than 15 - 1, so to run this line wrap needs to be true (which it will be if the player only pressed down rather than holding it) and in this case @index will now be (15 + 1) % 16 which gives us 0 and wraps it back to the top.
def cursor_up(wrap) @index = (@index - 1 + item_max) % item_max if @index > 0 || wrap ensure_cursor_visible end
This one handles the up arrow key, and does pretty much the opposite of cursor_down. This time though, the check we're making is @index greater than 0 or wrap, and the index setting is (@index minus 1 plus item_max) mod item_max. So say the index is 0 and the player just hit up, wrap is true so we set @index to (0 - 1 + 16) % 16 which gives us 15 and loops around to the bottom. This is a common method of doing wrapping selection windows.
def cursor_pagedown if top_index + visible_max < item_max self.top_index += visible_max @index = [@index + visible_max, item_max - 1].min end end
For pagedown, we're jumping multiple windows at a time. So first, we check whether top_index plus visible_max is less than item_max. If so, we add visible_max to self's top_index, then set @index to the minimum value between @index plus visible_max and item_max minus 1.
So for example, if our top index is currently 3 and @index is 5, 3 + 4 is less than 16, so the if statement runs. We add 4 to top_index so now it's 7, then set @index to the minimum between 5 + 4 and 15. 9 is less than 15, so we set it to 9.
def cursor_pageup if top_index > 0 self.top_index -= visible_max @index = [@index - visible_max, 0].max end end
And for pageup, we check whether top_index is greater than 0. If so, we subtract visible_max from self's top_index, and set @index to the maximum value between @index minus visible_max and 0.
So for example, if top_index returns 1, we set top_index to 1 - 4, but then the setter will set it to 0 since it's less than 0. @index is then set to the maximum of 0 - 4 and 0, which obviously returns 0.
def ensure_cursor_visible self.top_index = index if index < top_index self.bottom_index = index if index > bottom_index end
This method, well, ensures cursor visibility. So if index is less than top_index, we set self's top_index to index. And if index is greater than bottom_index, we set self's bottom_index to index. This just makes sure that if our index changes to one that isn't visible, it becomes visible afterwards.
That's it for the base scene! Now let's take a look at its kids. They're fairly light functionality-wise since the base class does most of the heavy lifting already.
Scene_Save
So here we have the scene for saving one's game. It inherits, obviously, from Scene_File.
def help_window_text Vocab::SaveMessage end
Our first port of call is to overwrite help_window_text to return Vocab::SaveMessage. If you take a quick gander back at Vocab, you'll see that SaveMessage returns "Save to which file?".
def first_savefile_index DataManager.last_savefile_index end
This method overwrites the one that returns 0, and instead we return the last_savefile_index property in DataManager, which will be the ID of the most recently accessed file.
def on_savefile_ok super if DataManager.save_game(@index) on_save_success else Sound.play_buzzer end end
This is the method that's called when the player hits ok on a save file. For starters, we call the parent method. Then if DataManager's save_game method returns true with @index passed to it, we call on_save_success. Otherwise, we call play_buzzer in the Sound module.
def on_save_success Sound.play_save return_scene end
Finally we have the method called when the player successfully saves a game. We call the Sound module's play_save method, then call return_scene.
And that's it! That's all that's needed for creating your save games. Crazy, huh?
Scene_Load
Scene_Load is actually almost identical. There's just a couple of differences in method names and on_load_success does a couple of extra things.
def help_window_text Vocab::LoadMessage end
So this method returns LoadMessage rather than SaveMessage. Nothing complicated.
def first_savefile_index DataManager.latest_savefile_index end
This one looks similar to the one in Scene_Save but actually isn't, since we're calling latest_savefile_index rather than last_savefile_index. The difference here is that latest returns the most recent save file by timestamp, rather than the last one *accessed*.
def on_savefile_ok super if DataManager.load_game(@index) on_load_success else Sound.play_buzzer end end
The only differences here are that we're calling load_game instead of save_game, and on_load_success instead of on_save_success.
def on_load_success Sound.play_load fadeout_all $game_system.on_after_load SceneManager.goto(Scene_Map) end
And finally, when a load is successful we play_load from Sound call fadeout_all (which fades the screen out), call on_after_load in $game_system (which sets the frame count and plays BGM/BGS) and finally we call SceneManager's goto method passing in Scene_Map, which...well, takes the player to the map scene.
Scene_End
This scene is the one the player goes to when they choose "Game End" in the menu. It inherits from Scene_MenuBase.
def start super create_command_window end
In our start method, first we call the parent method, then we call create_command_window.
def pre_terminate super close_command_window end
And in pre_terminate we call the parent method and then close_command_window.
def create_background super @background_sprite.tone.set(0, 0, 0, 128) end
This overwrite of create_background doesn't do much. First we call the parent method and then we set the tone of @background_sprite to 0 R, 0 G, 0 B and 128 grey, which effectively just makes the background darker.
def create_command_window @command_window = Window_GameEnd.new @command_window.set_handler(:to_title, method(:command_to_title)) @command_window.set_handler(:shutdown, method(:command_shutdown)) @command_window.set_handler(:cancel, method(:return_scene)) end
In create_command_window, first we set @command_window to a new instance of Window_GameEnd. Then we set the handlers for :to_title, :shutdown and :cancel to command_to_title, command_shutdown and return_scene respectively.
def close_command_window @command_window.close update until @command_window.close? end
This method is called from pre_terminate. First we call close on @command_window, and then call update until its close? method returns true. In other words, do nothing but perform frame updates until the window is fully closed.
def command_to_title close_command_window fadeout_all SceneManager.goto(Scene_Title) end
This method handles the "To Title" command. We call close_command_window, then fadeout_all to fade the screen, and finally SceneManager's goto method, passing in Scene_Title, to return the player to the title screen with no scene stack.
def command_shutdown close_command_window fadeout_all SceneManager.exit end
This method handles the "Shut down" command. Again, we call close_command_window and fadeout_all, but this time we call SceneManager.exit, which closes the game completely.
And that's it for Scene_End! We're fair flying through them today.
Scene_Shop
Another scene we'd be lost without. Without this one, the player would never be able to stock up on healing items, or buy a sweet new axe, or sell all those monster parts the party inexplicably picks up and carries around. The shop scene inherits from Scene_MenuBase.
def prepare(goods, purchase_only) @goods = goods @purchase_only = purchase_only end
The prepare method is called just after the shop scene is called in the shop processing event command, and takes as its parameters "goods", an array of the items the shop sells, and "purchase_only", a true/false flag denoting whether the player can only buy from the shop or can buy and sell. And in this method, we set @goods to goods and @purchase_only to purchase_only.
So this brings us full circle to be able to explain where the goods that go into Window_ShopBuy come from.
Each element of the array is itself an array containing the item type (0 for item, 1 for weapons, 2 for armour), item ID, whether the price is standard or custom (0 for standard, 1 for custom), and the price (which is only used if using custom). In addition, the first element will have a 5th parameter for purchase_only: true if the player can only buy, or false if they can buy and sell. For example, taking the default database, for a shop that sells standard Potion, Hi-Potion for 5g, standard Hand Ax, Gigantes Axe for half price, standard Casual Clothes and Mithril Plate for 5g, the array would be
[, , , , , ]
def start super create_help_window create_gold_window create_command_window create_dummy_window create_number_window create_status_window create_buy_window create_category_window create_sell_window end
In start, all we're doing is calling the parent method and then a bunch of window creation methods: for the help window, gold window, command window, dummy window, number window, status window buy window, category window and sell window. The only one that might not be self-explanatory is the dummy window, which I'll go over when we come to it.
def create_gold_window @gold_window = Window_Gold.new @gold_window.viewport = @viewport @gold_window.x = Graphics.width - @gold_window.width @gold_window.y = @help_window.height end
This method creates the gold window. We set @gold_window to a new instance of Window_Gold. Then we set that window's viewport to @viewport, its x coordinate to the width of the game window minus the width of the gold window, and the y coordinate to the height of the help window. This will position the gold display at the far right of the screen underneath the help window.
def create_command_window @command_window = Window_ShopCommand.new(@gold_window.x, @purchase_only) @command_window.viewport = @viewport @command_window.y = @help_window.height @command_window.set_handler(:buy, method(:command_buy)) @command_window.set_handler(:sell, method(:command_sell)) @command_window.set_handler(:cancel, method(:return_scene)) end
For the command window, we set @command_window to a new instance of Window_ShopCommand passing in @gold_window's x coordinate as the width and @purchase_only as purchase only. Then we set its viewport to @viewport and its y coordinate to the height of @help_window. This places the command window in the remaining horizontal space to the left of the gold window.
We then set the handlers for :buy, :sell and :cancel to command_buy, command_sell and return_scene respectively.
def create_dummy_window wy = @command_window.y + @command_window.height wh = Graphics.height - wy @dummy_window = Window_Base.new(0, wy, Graphics.width, wh) @dummy_window.viewport = @viewport end
This method creates the dummy window. First we set wy to @command-window's y plus its height, then wh to the game window height minus wy. Then we set @dummy_window to a new instance of Window_Base, passing in 0 as the x, wy as the y, the width of the game window as the width and wh as the height. This gives us a window on the far left of the screen, underneath the command window and taking up the remaining vertical space.
But what is the dummy window? It's just the big window that appears when the shop first opens, and exists only to fill up the space in an aesthetically-pleasing way until a command is chosen and there's other stuff to display.
def create_number_window wy = @dummy_window.y wh = @dummy_window.height @number_window = Window_ShopNumber.new(0, wy, wh) @number_window.viewport = @viewport @number_window.hide @number_window.set_handler(:ok, method(:on_number_ok)) @number_window.set_handler(:cancel, method(:on_number_cancel)) end
Now we create the number window. We set wy to @dummy_window's y coordinate, and wh to its height. Then we set @number_window to a new instance of Window_ShopNumber passing in 0 as the x, wy as the y, and wh as the height. This gives us a window on the far left of the screen, at the same y position as the dummy window and the same height as it. It won't be quite as wide, though.
We set @number_window's viewport to @viewport and call hide on it so that it won't be visible when the scene first opens. Then we set its :ok handler to on_number_ok, and its :cancel handler to on_number_cancel.
def create_status_window wx = @number_window.width wy = @dummy_window.y ww = Graphics.width - wx wh = @dummy_window.height @status_window = Window_ShopStatus.new(wx, wy, ww, wh) @status_window.viewport = @viewport @status_window.hide end
On to the status window. We set wx to the width of @number_window, wy to the y coordinate of @dummy_window, ww to the game window width minus wx, and wh to @dummy_window's height. Then we set @status_Window to a new instance of Window_ShopStatus passing in wx, wy, ww and wh as the x, y, width and height. This places the status window to the right of the number window taking up the rest of the horizontal space, at the same y coordinate as the dummy window and the same height. After that, we set @status_window's viewport to @viewport and hide it so that it isn't visible when the scene opens.
def create_buy_window wy = @dummy_window.y wh = @dummy_window.height @buy_window = Window_ShopBuy.new(0, wy, wh, @goods) @buy_window.viewport = @viewport @buy_window.help_window = @help_window @buy_window.status_window = @status_window @buy_window.hide @buy_window.set_handler(:ok, method(:on_buy_ok)) @buy_window.set_handler(:cancel, method(:on_buy_cancel)) end
This method creates the buy window. We set wy to @dummy_window's y, and wh to its height. Then we set @buy_window to a new instance of Window_ShopBuy passing in 0 as the x, wy as the y, wh as the height and @goods as the shop goods. This will result in the buy window being the same size and position as the number window.
Then we set @buy_window's viewport to @viewport, its help_window to @help_window and its status_window to @status_window, before hiding it. Then we set the :ok handler to on_buy_ok and the :cancel handler to on_buy_cancel.
def create_category_window @category_window = Window_ItemCategory.new @category_window.viewport = @viewport @category_window.help_window = @help_window @category_window.y = @dummy_window.y @category_window.hide.deactivate @category_window.set_handler(:ok, method(:on_category_ok)) @category_window.set_handler(:cancel, method(:on_category_cancel)) end
This method creates the category window. We set @category_window to a new instance of Window_ItemCategory, then set its viewport to @viewport, its help_window to @help_window, its y coordinate to @dummy_window's y, and then we hide and deactivate it (we need to do both because otherwise, since it's a selectable window, it will be active when the scene begins). Finally, we set the handler for :ok to on_category_ok, and the one for :cancel to on_category_cancel.
def create_sell_window wy = @category_window.y + @category_window.height wh = Graphics.height - wy @sell_window = Window_ShopSell.new(0, wy, Graphics.width, wh) @sell_window.viewport = @viewport @sell_window.help_window = @help_window @sell_window.hide @sell_window.set_handler(:ok, method(:on_sell_ok)) @sell_window.set_handler(:cancel, method(:on_sell_cancel)) @category_window.item_window = @sell_window end
Then we create the sell window. We set wy to @category_window's y plus its height, then wh to the game window height - wy, and set @sell_window to a new instance of Window_ShopSell, passing in 0 for the x, wy for the y, the width of the game window as the width and wh as the height. This will give us a window underneath the category window which takes up all remaining screen space. Then we set its viewport to @viewport and its help_window to @help_window before hiding it. We set the :ok handler to on_sell_ok and the :cancel handler to on_sell_cancel, and finally we set @category_window's item_window to @sell_window.
def activate_buy_window @buy_window.money = money @buy_window.show.activate @status_window.show end
This method activates the buy window. We set @buy_window's money property to the result of the money method, which is coming up in a bit. We show and activate @buy_window, and then show @status_window.
def activate_sell_window @category_window.show @sell_window.refresh @sell_window.show.activate @status_window.hide end
This method activates the sell window. Here we show @category_window, refresh, show and activate @sell_window, and hide @status_window.
def command_buy @dummy_window.hide activate_buy_window end
This method is called when the "Buy" command is selected. All we're doing here is hiding @dummy_window and calling activate_buy_window.
def command_sell @dummy_window.hide @category_window.show.activate @sell_window.show @sell_window.unselect @sell_window.refresh end
This method is called when the "Sell" command is selected. We hide @dummy_window, show and activate @category_window, then show, unselect and refresh @sell_window.
def on_buy_ok @item = @buy_window.item @buy_window.hide @number_window.set(@item, max_buy, buying_price, currency_unit) @number_window.show.activate end
This method is the handler for pressing :ok in the buy window. We set @item to @buy_window's item and hide @buy_window, then call @number_window's set method passing in @item for item, max_buy for max, buying_price for price and currency_unit for currency_unit, then show and activate @number_window.
def on_buy_cancel @command_window.activate @dummy_window.show @buy_window.hide @status_window.hide @status_window.item = nil @help_window.clear end
The handler for :cancel in the buy window. We activate @command_window and show @dummyWindow, then hide @buy_window and @status_window, set @status_window's item to nil, and clear @help_window.
def on_category_ok activate_sell_window @sell_window.select(0) end
This method handles hitting :ok in the category window. All we're doing is calling activate_sell_window and selecting index 0 in @sell_window.
def on_category_cancel @command_window.activate @dummy_window.show @category_window.hide @sell_window.hide end
This method handles hitting :cancel in the category window. We activate @command_window, show @dummy_window and then hide @category_window and @sell_window.
def on_sell_ok @item = @sell_window.item @status_window.item = @item @category_window.hide @sell_window.hide @number_window.set(@item, max_sell, selling_price, currency_unit) @number_window.show.activate @status_window.show end
This method handles hitting :ok in the sell window. We set @item to @sell_window's item and then set @status_window's item to @item. Then we hide the @category_window and sell_window. We call @number_window's set method passing in @item as item, max_sell as max, selling_price as price and currency_unit as currency_unit, and finally we show and activate @number_window and show @status_window.
def on_sell_cancel @sell_window.unselect @category_window.activate @status_window.item = nil @help_window.clear end
When hitting cancel in the sell window, we unselect @sell_window and activate @category_window. Then we set @status_window's item to nil and clear @help_window.
def on_number_ok Sound.play_shop case @command_window.current_symbol when :buy do_buy(@number_window.number) when :sell do_sell(@number_window.number) end end_number_input @gold_window.refresh @status_window.refresh end
This method handles hitting ok in the number window. We call play_shop in the Sound module, and then have a case (switch) statement on @command_window's current_symbol: if it's :buy, we call do_buy. If it's :sell, we call do_sell. In both cases we pass in @number_window's number value as the argument. After the case statement, we call end_number_input, then refresh the @gold_window and @status_window.
def on_number_cancel Sound.play_cancel end_number_input end
This method is called when hitting cancel in the number window. We call play_cancel in the Sound module and then end_number_input.
def do_buy(number) $game_party.lose_gold(number * buying_price) $game_party.gain_item(@item, number) end
This method processes final confirmation of buying an item (via hitting ok in the number window) and takes number as a parameter. We call $game_party's lose_gold method passing in number multiplied by buying_price, and its gain_item method passing in @item as the item and number as the quantity. This will remove X gold for each of the item bought, and add that number to the party's inventory.
def do_sell(number) $game_party.gain_gold(number * selling_price) $game_party.lose_item(@item, number) end
do_sell is effectively the opposite. We're calling gain_gold instead of lose_gold and lose_item instead of gain_item.
def end_number_input @number_window.hide case @command_window.current_symbol when :buy activate_buy_window when :sell activate_sell_window end end
This method ends the number input. We hide @number_window, and run a case statement against @command_window's current_symbol. If it's :buy, we call activate_buy_window, and if it's :sell we call activate_sell_window.
def max_buy max = $game_party.max_item_number(@item) - $game_party.item_number(@item) buying_price == 0 ? max : [max, money / buying_price].min end
This method determines the maximum number of the selected item which the party is ablve to buy.
We set max to $game_party's max_item_number passing in @item, minus its item_number passing in the same number. This gives the difference between how many of the item the party has and how many it can have. Then, if buying_price is 0, we return max, and if it isn't we return the minimum value between max and money divided by buying_price (which will give us the number the player can afford if that's less than the maximum they can hold).
def max_sell $game_party.item_number(@item) end
max_sell is a bit easier: we just return $game_party's item_number method passing in @item, because it stands to reason that the maximum number of a thing you can sell is the number of it that you have.
def money @gold_window.value end
The money method is just a wrapped for @gold_window's value. It's shorthand for how much gold the party has, basically.
def currency_unit @gold_window.currency_unit end
This method just returns @gold_window's currency_unit, which gets the currency unit from Vocab.
def buying_price @buy_window.price(@item) end
This method gets the buying price for the selected item. Here, we're just returning the value from @buy_window's price method when @item is passed to it.
def selling_price @item.price / 2 end
selling_price, on the other hand, returns half of @item's price.
And that's all there is to it!
We'll cut off there for this episode. I think I might be able to cover all the remaining classes next week, but it may require two episodes just because of how long Scene_Battle is. We'll see when we get there, I suppose.
Until next time!