=begin
#===============================================================================
Title: Shop Manager
Author: Tsukihime
Date: Mar 29, 2013
--------------------------------------------------------------------------------
** Change log
1.5 Mar 29
- added simple shop refreshing on page change. Will need a better solution
since this does not track the state of a shop on a different page
1.4 Mar 13
- fixed bug where common event shops were not handled appropriately when
called through effects
1.3 Mar 1
- Game_ShopGood now determines whether it should be included or enabled
- Two shop options implemented: hidden, disabled
1.2 Feb 25
- fixed issue where common event shops were not handled correctly
- exposed Shop Good variables as readers
1.1
- ShopManager handles shop scene changing depending on the type of shop
- added handling for different "types" of shops
- all goods in a shop can be accessed via shop.shop_goods
- reference to shop added to shop scene
- added support for adding and removing goods from a shop
1.0 Feb 22, 2013
- Initial Release
--------------------------------------------------------------------------------
** Terms of Use
* Free to use in commercial/non-commercial projects
* No real support. The script is provided as-is
* Will do bug fixes, but no compatibility patches
* Features may be requested but no guarantees, especially if it is non-trivial
* Credits to Tsukihime in your project
* Preserve this header
--------------------------------------------------------------------------------
** Description
This script serves as a base for all shop-related scripts.
This script provides functionality for remember a shop's "state".
Each shop is uniquely identified by a shop ID, and if you visit the same
shop repeatedly, you should see the same settings.
For example, if a shop holds 10 potions, and you bought 5, then you can
expect that the next time you visit the shop, it will only have 5 potions
remaining.
Of course, you will need extra plugins to have that kind of functionality.
Several new classes have been defined for working with shops, and in
particular, shop goods.
In summary:
-more control over your shops
-simple API for developers to write shop-related scripts
--------------------------------------------------------------------------------
** Usage
--------------------------------------------------------------------------------
** Developers
Here are some specifications that I have written.
Have a look through each class to see what is available for use.
If you have any suggestions that will improve the base script (ie: this),
I will consider adding them.
-- Shop Manager --
This module serves are the interface for all shop-related queries. It handles
how shops are stored for you so that it is easy to properly obtain a reference
to a shop.
You should use the Shop Manager whenever possible.
-- Game Shop --
This script treats a shop as a concrete object. A shop is created the first
time it is accessed, and will be stored with the game for the remainder of
the game.
A very basic shop is provided, which manages what items are available for sale.
All shops are stored in a global Game_Shops hash in $game_shops
-- Shop Type --
On top of the basic Game_Shop class, you can define your own shops and
associate them with custom scenes specific to those shops.
The Shop Manager only requires you to be consistent with your shop name.
For example, if your shop type is "CraftShop", then you must define a
Game_CraftShop - the shop object that the shop will be instantiated with
Scene_CraftShop - the scene that will be called when visiting a CraftShop
Users will set a @shop_type attribute in Game_Interpreter to determine
what type of shop should be created
-- Managing shops --
This script assumes shops are only called through events or common events.
Troop events are not supported.
A shop is identified by a map ID and an event ID.
Shops that are called via common events will have a map ID of 0.
In order to determine whether a normal event or a common event called the
shop, the "depth" of the interpreter is used.
When depth = 0, then it is a normal event
When depth > 0, then it is a common event
The shop processing should be handled appropriately depending on the depth
This script assumes that an event is triggered through "normal" interaction;
that is, you can only interact with events within a map. Any events that should
be treated as the same event should be done as a common event call.
--- Shop Goods ---
Rather than storing all goods as a simple array of values, this script
provides a Game_ShopGood class. You can use this to store any additional
information that you want.
All shop related scenes and windows MUST provide support for handling shop
goods. While backwards compatibility is provided for the default scripts,
additional methods have been defined to allow you to retrieve the currently
selected shop good.
--- Shop Options ---
Since there isn't actually a way to setup individual shop goods, external
approaches must be used. There is not much specification here yet, so it
is up to you how you wish to populate your ShopGood objects. I have provided
a "setup_goods" method in Game_Interpreter that you can alias.
--------------------------------------------------------------------------------
** Compatibility
This script changes the following from the default scripts
DataManager
aliased - create_game_objects
aliased - make_save_contents
aliased - extract_save_contents
Game_Interpreter
replaced - command_302
Window_ShopBuy
replaced - prepare
Scene_Shop
replaced - make_item_list
#===============================================================================
=end
$imported = {} if $imported.nil?
$imported["TH_ShopManager"] = 1.5
#===============================================================================
# ** Rest of the Script
#===============================================================================
#-------------------------------------------------------------------------------
# Main shop manager that acts as the interface between shops and other objects
# The main role of the ShopManager is to essentially manage any shop objects
# that exist in the game. In particular, it provides all of the methods for
# creating, retrieving, and deleting shops.
#-------------------------------------------------------------------------------
module ShopManager
#-----------------------------------------------------------------------------
# Return a reference to a specific shop
#-----------------------------------------------------------------------------
def self.get_shop(map_id, event_id)
return $game_shops[map_id, event_id]
end
#-----------------------------------------------------------------------------
# Indicate that a shop needs to be refreshed
#-----------------------------------------------------------------------------
def self.refresh_shop(map_id, event_id)
shop = get_shop(map_id, event_id)
shop.need_refresh = true if shop
end
#-----------------------------------------------------------------------------
# Setup shop if first time visiting
#-----------------------------------------------------------------------------
def self.setup_shop(map_id, event_id, goods, purchase_only, shop_type)
shop = get_shop(map_id, event_id)
return shop if shop && !shop.need_refresh
shop = shop_class(shop_type).new(goods, purchase_only)
$game_shops[map_id, event_id] = shop
return shop
end
#-----------------------------------------------------------------------------
# Return the appropriate shop class given the shop type
#-----------------------------------------------------------------------------
def self.shop_class(shop_type)
shop_type = "Game_" + shop_type.to_s
return Object.const_get(shop_type.to_sym)
end
#-----------------------------------------------------------------------------
# Return the scene associated with this shop.
# TO DO
#-----------------------------------------------------------------------------
def self.shop_scene(shop)
shop_scene = "Scene_" + shop.class.name.gsub("Game_", "")
return Object.const_get(shop_scene.to_sym)
end
#-----------------------------------------------------------------------------
# Invokes SceneManager.call on the appropriate scene
#-----------------------------------------------------------------------------
def self.call_scene(shop)
SceneManager.call(shop_scene(shop))
SceneManager.scene.prepare(shop)
end
#-----------------------------------------------------------------------------
# Invokes SceneManager.goto on the appropriate scene
#-----------------------------------------------------------------------------
def self.goto_scene(shop)
SceneManager.goto(shop_scene(shop))
SceneManager.scene.prepare(shop)
end
#-----------------------------------------------------------------------------
# Returns a good ID, given a shop and an item. If the item is already in
# the shop, it will return that good's ID. Otherwise, it will return a new ID
#-----------------------------------------------------------------------------
def self.get_good_id(shop, item)
good = shop.shop_goods.detect {|good| good.item == item}
return good.nil? ? shop.shop_goods.size + 1 : good.id
end
#-----------------------------------------------------------------------------
# Returns a good, given a shop and an item. If the shop already has that good
# just return it. Otherwise, make a new good. If the price is negative, then
# the price is the default price. Otherwise, it is the specified price.
#-----------------------------------------------------------------------------
def self.get_good(shop, item, price=-1)
good = shop.shop_goods.detect {|good| good.item == item}
return good if good
good_id = shop.shop_goods.size + 1
type = item_type(item)
if price < 0
price_type = price = 0
else
price_type = 1
end
return Game_ShopGood.new(good_id, type, item.id, price_type, price)
end
#-----------------------------------------------------------------------------
# Returns the type of an item.
#-----------------------------------------------------------------------------
def self.item_type(item)
return 0 if item.is_a?(RPG::Item)
return 1 if item.is_a?(RPG::Weapon)
return 2 if item.is_a?(RPG::Armor)
return -1
end
end
#-------------------------------------------------------------------------------
# Shops are stored in a global variable `$game_shops`. This is dumped and
# loaded appropriately.
#-------------------------------------------------------------------------------
module DataManager
class << self
alias :th_shop_manager_create_game_objects :create_game_objects
alias :th_shop_manager_make_save_contents :make_save_contents
alias :th_shop_manager_extract_save_contents :extract_save_contents
end
def self.create_game_objects
th_shop_manager_create_game_objects
$game_shops = Game_Shops.new
end
def self.make_save_contents
contents = th_shop_manager_make_save_contents
contents[:shops] = $game_shops
contents
end
#-----------------------------------------------------------------------------
# Load shop data
#-----------------------------------------------------------------------------
def self.extract_save_contents(contents)
th_shop_manager_extract_save_contents(contents)
$game_shops = contents[:shops]
end
end
class Game_Temp
# even if we're not actually calling a shop, it shouldn't affect anything
# because we are always setting this at each common event call by an event
attr_accessor :shop_common_event_id
alias :th_shop_manager_reserve_common_event :reserve_common_event
def reserve_common_event(common_event_id)
th_shop_manager_reserve_common_event(common_event_id)
@shop_common_event_id = common_event_id
end
end
class Game_Event < Game_Character
alias :th_shop_manager_setup_page :setup_page
def setup_page(page)
th_shop_manager_setup_page(page)
ShopManager.refresh_shop(@map_id, @id)
end
end
class Game_Interpreter
alias :th_shop_manager_clear :clear
def clear
th_shop_manager_clear
clear_shop_options
@shop_type = nil
end
#-----------------------------------------------------------------------------
# New.
#-----------------------------------------------------------------------------
def clear_shop_options
@shop_options = {}
@shop_options[:hidden] = {}
@shop_options[:disabled] = {}
end
#-----------------------------------------------------------------------------
# New. We are in a common event only if the shop common event ID is set.
#-----------------------------------------------------------------------------
def shop_map_id
$game_temp.shop_common_event_id ? 0 : @map_id
end
def shop_event_id
$game_temp.shop_common_event_id ? $game_temp.shop_common_event_id : @event_id
end
#--------------------------------------------------------------------------
# Set the shop's common event ID
#--------------------------------------------------------------------------
alias :th_shop_manager_command_117 :command_117
def command_117
$game_temp.shop_common_event_id = @params[0]
th_shop_manager_command_117
end
#-----------------------------------------------------------------------------
# Replaced. A shop is setup only once, and is retrieved whenever it is
# called in the future. This assumes the shop is invoked through "normal"
# event interactions.
#-----------------------------------------------------------------------------
def command_302
return if $game_party.in_battle
shop_type = @shop_type || :Shop
good_id = 1
goods = []
# setup goods
good = make_good(@params[0..-1], good_id) # last param is for the shop
goods.push(good)
good_id +=1
while next_event_code == 605
@Index += 1
good = make_good(@list[@index].parameters, good_id)
goods.push(good)
good_id +=1
end
# Setup shop if needed
shop = ShopManager.setup_shop(shop_map_id, shop_event_id, goods, @params[4], shop_type)
# prepare the shop with a reference to the actual shop
ShopManager.call_scene(shop)
Fiber.yield
# clear out the shop common event ID.
$game_temp.shop_common_event_id = nil
end
#-----------------------------------------------------------------------------
# New. This is where the goods are setup.
#-----------------------------------------------------------------------------
def make_good(goods_array, good_id)
item_type, item_id, price_type, price = goods_array
good = Game_ShopGood.new(good_id, item_type, item_id, price_type, price)
# additional setup
setup_good(good, good_id)
return good
end
#-----------------------------------------------------------------------------
# New. You can do more things with your good here
#-----------------------------------------------------------------------------
def setup_good(good, good_id)
setup_hidden_option(good, good_id)
setup_disabled_option(good, good_id)
end
#-----------------------------------------------------------------------------
# New. Shop options
#-----------------------------------------------------------------------------
def hide_good(good_id, condition)
@shop_options[:hidden][good_id] = condition
end
def disable_good(good_id, condition)
@shop_options[:disabled][good_id] = condition
end
def setup_hidden_option(good, good_id)
return unless @shop_options[:hidden][good_id]
good.hidden_condition = @shop_options[:hidden][good_id]
end
def setup_disabled_option(good, good_id)
return unless @shop_options[:disabled][good_id]
good.disable_condition = @shop_options[:disabled][good_id]
end
end
#-------------------------------------------------------------------------------
# A shop good.
# This is a wrapper around a raw item (RPG::Item, RPG::Weapon, etc).
#-------------------------------------------------------------------------------
class Game_ShopGood
attr_reader :id # ID of this good
attr_reader :item_type
attr_reader :item_id
attr_reader :price_type
attr_accessor :hidden_condition
attr_accessor :disable_condition
def initialize(id, item_type, item_id, price_type, price)
@id = id
@item_type = item_type
@item_id = item_id
@price_type = price_type
@price = price
@hidden_condition = ""
@disable_condition = ""
end
def include?
return false if eval(@hidden_condition)
return true
end
def enable?
return false if eval(@disable_condition)
return true
end
def item
return $data_items[@item_id] if @item_type == 0
return $data_weapons[@item_id] if @item_type == 1
return $data_armors[@item_id] if @item_type == 2
end
def price
return item.price if @price_type == 0
return @price
end
end
#-------------------------------------------------------------------------------
# A shop object. Stores information about the shop such as its inventory
# and other shop-related data
#-------------------------------------------------------------------------------
class Game_Shop
attr_reader :purchase_only
attr_reader :shop_goods # all goods that this shop has.
attr_accessor :need_refresh # shop needs to be refreshed
def initialize(goods, purchase_only=false)
@shop_goods = goods
@purchase_only = purchase_only
@need_refresh = false
end
#-----------------------------------------------------------------------------
# Returns true if the goods should be included
#-----------------------------------------------------------------------------
def include?(index)
true
end
#-----------------------------------------------------------------------------
# Return a set of goods for sale
#-----------------------------------------------------------------------------
def goods
@shop_goods
end
#-----------------------------------------------------------------------------
# Add a new good to the shop
#-----------------------------------------------------------------------------
def add_good(good)
@shop_goods.push(good) unless @shop_goods.include?(good)
end
#-----------------------------------------------------------------------------
# Remove the specified good from the shop
#-----------------------------------------------------------------------------
def remove_good(good_id)
@shop_goods.delete_at(good_id - 1)
end
end
#-------------------------------------------------------------------------------
# A wrapper containing all shops in the game.
#-------------------------------------------------------------------------------
class Game_Shops
#-----------------------------------------------------------------------------
# Initializes a hash of game shops. Each key is a map ID, pointing to another
# hash whose keys are event ID's and values are Game_Shop objects.
#-----------------------------------------------------------------------------
def initialize
@data = {}
end
def [](i, j)
@data[i] ||= {}
@data[i][j]
end
def []=(i, j, shop)
@data[i] ||= {}
@data[i][j] = shop
end
end
#-------------------------------------------------------------------------------
# The shop scene now works with the Shop and ShopGood objects
#-------------------------------------------------------------------------------
class Scene_Shop < Scene_MenuBase
#--------------------------------------------------------------------------
# Replaced. The scene now takes a Game_Shop object
#--------------------------------------------------------------------------
def prepare(shop)
@shop = shop
@goods = shop.goods
@purchase_only = shop.purchase_only
end
alias :th_shop_manager_on_buy_ok :on_buy_ok
def on_buy_ok
@selected_good = @buy_window.current_good
th_shop_manager_on_buy_ok
end
end
#-------------------------------------------------------------------------------
# @shop_goods is now an array of Game_ShopGoods
#-------------------------------------------------------------------------------
class Window_ShopBuy < Window_Selectable
#--------------------------------------------------------------------------
# New. Returns true if the good should be included in the shop inventory
#--------------------------------------------------------------------------
def include?(shopGood)
shopGood.include?
end
alias :th_shop_manager_enable? :enable?
def enable?(item)
return false unless @goods[item].enable?
th_shop_manager_enable?(item)
end
#--------------------------------------------------------------------------
# New. Get the currently selected good
#--------------------------------------------------------------------------
def current_good
@goods[item]
end
#-----------------------------------------------------------------------------
# Replaced. ShopGood takes care of most information. The data still contains
# a list of RPG items for now since I don't want to change them to goods yet
# A separate list of goods for sale is used for 1-1 correspondence
#-----------------------------------------------------------------------------
def make_item_list
@data = []
@goods = {}
@price = {}
@shop_goods.each do |shopGood|
next unless include?(shopGood)
item = shopGood.item
@data.push(item)
@goods[item] = shopGood
@price[item] = shopGood.price
end
end
end