# =============================================================================
# TheoAllen - (像辐射的) 合成系统
# Version : 1.1
# Contact : [url]www.rpgmakerid.com[/url] (or) [url]http://theolized.blogspot.com[/url]
# (English documentation)
# =============================================================================
($imported ||= {})[:Theo_Crafting] = true
# =============================================================================
# CHANGE LOGS:
# -----------------------------------------------------------------------------
# 2013.08.25 - Bugfix disable req text isn't working
# 2013.08.22 - Bugfix at amount of item crafting
#            - Added show condition
#            - Added custom name
# 2013.08.20 - Finished script
# 2013.08.19 - Started script
# =============================================================================
=begin
 
  ---------------------------------------------------------------------------
  介绍 :
  本脚本可以根据原料合成物品
  
  ---------------------------------------------------------------------------
  使用方法 :
  ---------------------------------------------------------------------------
  本脚本放在插件脚本之下,main之上
  使用脚本来显示合成画面 : 
  
  enter_crafting
  
  ---------------------------------------------------------------------------
  物品/武器/护甲备注:
  ---------------------------------------------------------------------------
  -----------------------------------
  简单的备注
  -----------------------------------
  <add item recipe: id,数量>
  <add armor recipe: id,数量>
  <add weapon recipe: id,数量>
  ^
  以上备注可以确定该物品/武器/护甲需要物品/护甲/武器作为合成原料,id为原料id,
  数量为需要原料的数量
  
  <craft name: 文本>
  ^
  暂时改变物品名称的备注.例如,在你的合成列表中,回复剂会显示名称“合成回复剂”
  但是当你成功合成回复剂,且得到它后,它的名称会变回初始名称。
  -----------------------------------
  复杂的备注
  (如果你不懂,就不要尝试)
  -----------------------------------
  <craft require>
  脚本
  </craft require>
  ^
  设定物品的合成条件,由脚本作为判断条件。写作多行时会视为同一行。我在别处写了
  可以调用的脚本指令,可是..用的是印尼语。
  
  错误的书写会使游戏崩溃,所以请确定书写规范.
  
  <craft req text>
  文本
  </craft req text>
  ^
  用于告诉玩家合成的条件。例如,如果合成某物品需要一把银色的锤子,则你可以这样写:
  
  <craft req text>
  合成这个物品需要一把银色的锤子
  </craft req text>
  
  <craft show eval>
  脚本
  </craft show eval>
  ^
  以上备注用来确定物品是否会出现在合成列表中。例如,合成圣灵药时,需要角色学会
  高等级的炼金术,所以在角色学会特定技能前不会显示于合成列表中。
  
  错误的书写会使游戏崩溃,所以请确定书写规范.
  
  ---------------------------------------------------------------------------
  使用规定 :
  属名脚本作者, TheoAllen. 你可以自由编辑此脚本,只要你不声明你是脚本的原作者
  如果你想用此脚本于商业游戏,请和我共享收益.别忘了给我一份免费的游戏拷贝.
 
=end
# =============================================================================
# 设定 :
# =============================================================================
module THEO
  module Craft
 
  # --------------------------------------------------------------------------
  # 通用设定 (true/false)
  # --------------------------------------------------------------------------
    MainMenu        = true
  # 是否在菜单中显示"合成"指令
 
    DisplayReqText  = true
  # 对于那些不想使用合成条件文本的人,你可以在这里禁用它
 
  # --------------------------------------------------------------------------
  # 用语设置 (文本/字符串)
  # --------------------------------------------------------------------------
    VocabMenu      = "合成"          # 主菜单的合成指令
    VocabNoReq     = "- 无 -"        # 无任何合成条件
    VocabRequire   = "合成条件 :"    # 合成条件
    VocabRecipes   = "原料 : "       # 原料
    VocabInventory = "持有量: "      # 持有量
 
  # --------------------------------------------------------------------------
  # 杂项 (使用数字来设定 / 0123456789)
  # --------------------------------------------------------------------------
    Text_Buffer_x = 12
  # 文本右移距离
 
    AmountSize = 250
  # 数量窗口的宽度
 
  end
end
# =============================================================================
# 以下勿动
# =============================================================================
module THEO
  module Craft
    def self.items
      items = $data_items + $data_armors + $data_weapons
      items.compact!
      items.select {|item| item.can_be_crafted? }
    end
  end
end
 
class RPG::BaseItem
  attr_accessor :item_recipes
  attr_accessor :armor_recipes
  attr_accessor :weapon_recipes
  attr_accessor :enable_cond
  attr_accessor :enable_text
  attr_accessor :show_cond
  attr_accessor :craft_name
  attr_accessor :craft_key  # For future planned feature
 
  def load_recipes
    @item_recipes = {}
    @armor_recipes = {}
    @weapon_recipes = {}
    @show_cond = @enable_cond = "true"
    @enable_text = []
    @craft_name = ""
    @craft_key = ""
    read_craft_cond = read_craft_txt = read_craft_show = false
    self.note.split(/[\r\n]+/).each do |line|
      case line
      when /<(?:ADD_ITEM_RECIPE|add item recipe): (.*)>/i
        str = $1.scan(/\d+/)
        @item_recipes[str[0].to_i] = str[1].to_i
      when /<(?:ADD_ARMOR_RECIPE|add armor recipe): (.*)>/i
        str = $1.scan(/\d+/)
        @armor_recipes[str[0].to_i] = str[1].to_i
      when /<(?:ADD_WEAPON_RECIPE|add WEAPON recipe): (.*)>/i
        str = $1.scan(/\d+/)
        @weapon_recipes[str[0].to_i] = str[1].to_i
      when /<(?:CRAFT_REQUIRE|craft require)>/i
        read_craft_cond = true
        @enable_cond = ""
      when /<\/(?:CRAFT_REQUIRE|craft require)>/i
        read_craft_cond = false
      when /<(?:CRAFT_REQ_TEXT|craft req text)>/i
        read_craft_txt = true
      when /<\/(?:CRAFT_REQ_TEXT|craft req text)>/i
        read_craft_txt = false
      when /<(?:CRAFT_SHOW_EVAL|craft show eval)>/i
        read_craft_show = true
        @show_cond = ""
      when /<\/(?:CRAFT_SHOW_EVAL|craft show eval)>/i
        read_craft_show = false
      when /<(?:CRAFT_NAME|craft name): (.*)>/i
        @craft_name = $1.to_s
      when /<(?:CRAFT_KEY|craft key): [ ]*(.*)>/i
        @craft_key = $1.to_s
      else
        if read_craft_cond
          @enable_cond += line
        end
        if read_craft_txt
          @enable_text.push(line)
        end
        if read_craft_show
          @show_cond += line
        end
      end
    end
  end
 
  def recipes
    [@item_recipes, @armor_recipes, @weapon_recipes]
  end
 
  # ---------------------------------------------------------------------------
  # If the error is directed you to this line. It's possible that you wrote
  # craft requirement formula in a wrong way. Not this script mistake
  # ---------------------------------------------------------------------------
  def craft_possible?
    return eval(@enable_cond) && can_be_crafted?
  end
 
  def show_possible?
    return eval(@show_cond)
  end
  # ---------------------------------------------------------------------------
  # Determine if item can be crafted
  # ---------------------------------------------------------------------------
  def can_be_crafted?
    recipes.any? {|recipe| !recipe.empty?} && show_possible?
  end
 
end
 
class << DataManager
 
  alias theo_recipe_craft_load_db load_database
  def load_database
    theo_recipe_craft_load_db
    load_item_recipes
  end
 
  def load_item_recipes
    ($data_items+$data_armors+$data_weapons).compact.each do |db|
      db.load_recipes
    end
  end
 
end
 
class Window_CraftList < Window_ItemList
 
  def initialize(x,y,w,h,key)
    @key = key
    super(x,y,w,h)
    refresh
  end
 
  def col_max
    return 1
  end
 
  def status_window=(window)
    @status_window = window
    update_help
  end
 
  def item_number_window=(window)
    @inum_window = window
    update_help
  end
 
  def update_help
    super
    @status_window.set_item(item) if @status_window
    @inum_window.set_item(item) if @inum_window
  end
 
  def make_item_list
    @data = []
    items = THEO::Craft.items
    items.each do |item|
      @data.push(item) if @key.empty? || item.craft_key == @key
    end
  end
 
  def enable?(item)
    item.craft_possible? && has_recipes?(item) && has_free_slot?(item)
  end
 
  def has_free_slot?(item)
    $game_party.item_number(item) < $game_party.max_item_number(item)
  end
 
  def has_recipes?(item)
    result1 = check_recipe(item.recipes[0], $data_items)
    result2 = check_recipe(item.recipes[1], $data_armors)
    result3 = check_recipe(item.recipes[2], $data_weapons)
    return result1 && result2 && result3
  end
 
  def check_recipe(recipes, data)
    return true if recipes.empty?
    return recipes.all? {|recipe| 
      $game_party.item_number(data[recipe[0]]) >= recipe[1]} 
  end
 
  def draw_item_name(item, x, y, enabled = true, width = 172)
    return unless item
    draw_icon(item.icon_index, x, y, enabled)
    change_color(normal_color, enabled)
    name = item.craft_name.empty? ? item.name : item.craft_name
    draw_text(x + 24, y, width, line_height, name)
  end
 
  def draw_item_number(rect, item)
    return unless $imported[:Theo_LimInventory]
    return unless THEO::LimInv::Display_ItemSize
    draw_text(rect, sprintf(":%2d", item.inv_size), 2)
  end
 
end
 
class Window_CraftStatus < Window_Base
 
  def set_item(item)
    @item = item
    refresh
  end
 
  def refresh
    contents.clear
    return unless @item
    @line_count = 0
    draw_requirement
    draw_recipes
  end
 
  def draw_requirement
    return unless THEO::Craft::DisplayReqText
    change_color(system_color)
    draw_text(line_rect(4),THEO::Craft::VocabRequire)
    increase_line_count
    if !@item.enable_text.empty?
      change_color(normal_color, @item.craft_possible?)
      @item.enable_text.each do |text|
        draw_text(line_rect(buff_x),text)
        increase_line_count
      end
      increase_line_count
    else
      change_color(normal_color)
      draw_text(line_rect(buff_x),THEO::Craft::VocabNoReq)
      2.times { increase_line_count }
    end
  end
 
  def draw_recipes
    change_color(system_color)
    draw_text(line_rect(4),THEO::Craft::VocabRecipes)
    change_color(normal_color)
    increase_line_count
    draw_recipe(@item.recipes[0], $data_items)
    draw_recipe(@item.recipes[1], $data_armors)
    draw_recipe(@item.recipes[2], $data_weapons)
  end
 
  def draw_recipe(hash, items)
    hash.each do |id,amount|
      item_num = $game_party.item_number(items[id])
      enable = item_num >= amount
      change_color(normal_color, enable)
      rect = line_rect(buff_x + 24)
      text = sprintf("%s (%d/%d)",items[id].name, item_num, amount)
      draw_text(rect, text)
      draw_icon(items[id].icon_index, buff_x, @line_count, enable)
      increase_line_count
    end
  end  
 
  def line_rect(xpos = 0)
    Rect.new(xpos,@line_count,contents.width-xpos,line_height)
  end
 
  def increase_line_count
    @line_count += line_height
  end
 
  def buff_x
    THEO::Craft::Text_Buffer_x
  end
 
end
 
class Window_CraftAmount < Window_Base
  attr_accessor :inv_window
  attr_accessor :ok_handler
  attr_accessor :cancel_handler
  attr_reader :amount
 
  def initialize
    super(0,0,window_width,fitting_height(1))
    self.openness = 0
    to_center
    @amount = 0
    @max = 0
    @item = nil
  end
 
  def window_width
    return THEO::Craft::AmountSize
  end
 
  def to_center
    self.x = (Graphics.width - width)/2
    self.y = (Graphics.height - height)/2
  end
 
  def set(item, max)
    change_amount(0,true)
    @item = item
    @max = max
    open
    refresh
  end
 
  def refresh
    contents.clear
    return unless @item
    draw_item_name(@item,0,0,true,contents.width)
    draw_amount
  end
 
  def draw_amount
    text = sprintf("%d/%d",@amount,@max)
    draw_text(0,0,contents.width,line_height,text,2)
  end
 
  def update
    super
    if open?
      change_amount(1) if Input.repeat?(:RIGHT)
      change_amount(-1) if Input.repeat?(:LEFT)
      change_amount(10) if Input.repeat?(:UP)
      change_amount(-10) if Input.repeat?(:DOWN)
      call_ok if Input.trigger?(:C)
      call_cancel if Input.trigger?(:B)
    end
  end
 
  def change_amount(number, instant = false)
    if instant
      @amount = number
    else
      Sound.play_cursor
      @amount = [[@amount + number,0].max,@max].min
    end
    refresh
    @inv_window.amount = @amount if @inv_window
  end
 
  def call_ok
    Sound.play_ok
    ok_handler.call
    change_amount(0,true)
    close
  end
 
  def call_cancel
    Sound.play_cancel
    cancel_handler.call
    change_amount(0,true)
    close
  end
 
end
 
class Window_CraftFooter < Window_Base
 
  def initialize
    super(0,0,Graphics.width,fitting_height(1))
    self.y = Graphics.height - height
    @amount = 0
    @item = nil
    refresh
  end
 
  def amount=(amount)
    @amount = amount
    refresh
  end
 
  def refresh
    contents.clear
    change_color(system_color)
    draw_text(0,0,contents.width,line_height,THEO::Craft::VocabInventory)
    change_color(normal_color)
    return unless @item
    if $imported[:Theo_LimInventory]
      draw_inv_slot(0,0)
    else
      draw_item_number
    end
  end
 
  def draw_inv_slot(x,y,width = contents.width,align = 2)
    total = $game_party.total_inv_size + @amount*@item.inv_size
    total -= calculate_lose_item
    txt = sprintf("%d/%d", total, $game_party.inv_max)
    color = THEO::LimInv::NearMaxed_Color
    near_max = total.to_f/$game_party.inv_max >= 
      (100 - THEO::LimInv::NearMaxed_Percent).to_f / 100 
    if near_max
      change_color(text_color(color))
    else
      change_color(normal_color)
    end
    draw_text(x,y,width,line_height,txt,align)
    change_color(normal_color)
  end
 
  def draw_item_number
    txt = sprintf("%d/%d",@amount + $game_party.item_number(@item),
      $game_party.max_item_number(@item))
    draw_text(0,0,contents.width,line_height,txt,2)
  end
 
  def set_item(item)
    @item = item
    refresh
  end
 
  def calculate_lose_item
    result = 0
    @item.recipes[0].each do |id,amount|
      data = $data_items[id]
      amount *= @amount
      result += amount
    end
    @item.recipes[1].each do |id,amount|
      data = $data_armors[id]
      amount *= @amount
      result += amount
    end
    @item.recipes[2].each do |id,amount|
      data = $data_weapons[id]
      amount *= @amount
      result += amount
    end
    return result
  end
 
end
 
class Game_Interpreter
 
  # ----------------------------------------------
  # key is just for my future planned feature
  # ----------------------------------------------
  def enter_crafting(key = "")
    SceneManager.call(Scene_ItemCrafting)
    SceneManager.scene.prepare(key)
    Fiber.yield
  end
 
end
 
class Window_MenuCommand < Window_Command
 
  alias theo_craft_ori_cmd add_original_commands
  def add_original_commands
    theo_craft_ori_cmd
    return unless THEO::Craft::MainMenu
    add_command(THEO::Craft::VocabMenu, :craft)
  end
 
end
 
class Scene_Menu < Scene_MenuBase
 
  alias theo_craft_cmnd_window create_command_window
  def create_command_window
    theo_craft_cmnd_window
    @command_window.set_handler(:craft, method(:enter_crafting))
  end
 
  def enter_crafting
    SceneManager.call(Scene_ItemCrafting)
    SceneManager.scene.prepare("")
  end
 
end
 
class Scene_ItemCrafting < Scene_MenuBase
 
  def prepare(key)
    @key = key
  end
 
  def start
    super
    create_help_window
    create_craftfooter_window
    create_craftlist_window
    create_craftstatus_window
    create_craftamount_window
  end
 
  def create_craftfooter_window
    @craft_footer = Window_CraftFooter.new
  end
 
  def create_craftlist_window
    wy = @help_window.height
    ww = Graphics.width/2
    wh = Graphics.height - wy - @craft_footer.height
    @craft_list = Window_CraftList.new(0,wy,ww,wh,@key)
    @craft_list.set_handler(:ok, method(:on_craft_ok))
    @craft_list.set_handler(:cancel, method(:return_scene))
    @craft_list.help_window = @help_window
    @craft_list.item_number_window = @craft_footer
    @craft_list.activate
    @craft_list.select(0)
  end
 
  def create_craftstatus_window
    wx = @craft_list.width
    wy = @craft_list.y
    ww = @craft_list.width
    wh = @craft_list.height
    @craft_status = Window_CraftStatus.new(wx,wy,ww,wh)
    @craft_list.status_window = @craft_status
  end
 
  def create_craftamount_window
    @craft_amount = Window_CraftAmount.new
    @craft_amount.ok_handler = method(:on_amount_ok)
    @craft_amount.cancel_handler = method(:on_amount_cancel)
    @craft_amount.inv_window = @craft_footer
  end
 
  def on_craft_ok
    @craft_amount.set(item, item_max)
  end
 
  def on_amount_ok
    $game_party.gain_item(item, @craft_amount.amount)
    lose_recipes
    @craft_status.refresh
    @craft_list.refresh
    @craft_list.activate
  end
 
  def on_amount_cancel
    @craft_list.activate
  end
 
  def item
    @craft_list.item
  end
 
  def item_max
    ary = []
    item.recipes[0].each do |id,amount|
      data = $data_items[id]
      ary.push($game_party.item_number(data)/amount)
    end
    item.recipes[1].each do |id,amount|
      data = $data_armors[id]
      ary.push($game_party.item_number(data)/amount)
    end
    item.recipes[2].each do |id,amount|
      data = $data_weapons[id]
      ary.push($game_party.item_number(data)/amount)
    end
    max_item = $game_party.max_item_number(item)
    inum = $game_party.item_number(item)
    return [ary.min , max_item - inum].min
  end
 
  def lose_recipes
    item.recipes[0].each do |id,amount|
      data = $data_items[id]
      amount *= @craft_amount.amount
      $game_party.lose_item(data,amount)
    end
    item.recipes[1].each do |id,amount|
      data = $data_armors[id]
      amount *= @craft_amount.amount
      $game_party.lose_item(data,amount)
    end
    item.recipes[2].each do |id,amount|
      data = $data_weapons[id]
      amount *= @craft_amount.amount
      $game_party.lose_item(data,amount)
    end
  end
 
end