游戏常数设置
# @taroxd metadata 1.0
# @display 游戏常数设置
# @require taroxd_core
# @id const
module Taroxd::Const

  # 游戏常数设置区域(如果要用默认值,可以设置为 false 或直接删除)

  SAVEFILE_MAX = 16               # 存档文件的最大个数
  ESCAPE_RATIO_UP = 0.1           # 撤退失败后,撤退成功率提升值
  MAX_TP = 100                    # TP 的最大值
  ATTACK_SKILL_ID = 1             # 默认攻击技能 ID
  GUARD_SKILL_ID = 2              # 默认防御技能 ID
  DEATH_STATE_ID = 1              # 默认死亡状态 ID
  PARAM_LIMIT = 999999            # 能力值的最大值
  PRESERVE_TP = false             # 是否永远特技专注
  LUK_EFFECT_RATE = 0.001         # 幸运值影响程度
  CRITICAL_RATE = 3               # 关键一击伤害倍率
  STEPS_FOR_RUN = 20              # 地图上多少步等于一回合
  BASIC_FLOOR_DAMAGE = 10         # 地形伤害的基础值
  MAX_BATTLE_MEMBERS = 4          # 参战角色的最大数
  MAX_GOLD = 99999999             # 持有金钱的最大值
  MAX_ITEM_NUMBER = 99            # 物品的最大持有数
  BUSH_DEPTH = 8                  # 流体地形的深度
  BUSH_OPACITY = 128              # 流体地形的不透明度
  PLAYER_INITIAL_DIRECTION = 2    # 角色初始朝向
  SUBSTITUTE_HP_RATE = 0.25       # HP 比率达到多少以下会触发保护弱者
  Font.default_name = 'nsimsun'   # 默认字体名称
  Font.default_size = 24          # 默认字体大小

  def self.[](sym)
    const_defined?(sym, false) && const_get(sym)
  end
end

def DataManager.savefile_max
  Taroxd::Const::SAVEFILE_MAX
end if Taroxd::Const[:SAVEFILE_MAX]

def BattleManager.process_escape
  $game_message.add(sprintf(Vocab::EscapeStart, $game_party.name))
  success = @preemptive ? true : (rand < @escape_ratio)
  Sound.play_escape
  if success
    process_abort
  else
    @escape_ratio += Taroxd::Const::ESCAPE_RATIO_UP
    $game_message.add('\.' + Vocab::EscapeFailure)
    $game_party.clear_actions
  end
  wait_for_message
  success
end if Taroxd::Const[:ESCAPE_RATIO_UP]

class Game_BattlerBase

  def max_tp
    Taroxd::Const::MAX_TP
  end if Taroxd::Const[:MAX_TP]

  def attack_skill_id
    Taroxd::Const::ATTACK_SKILL_ID
  end if Taroxd::Const[:ATTACK_SKILL_ID]

  def guard_skill_id
    Taroxd::Const::GUARD_SKILL_ID
  end if Taroxd::Const[:GUARD_SKILL_ID]

  def death_state_id
    Taroxd::Const::DEATH_STATE_ID
  end if Taroxd::Const[:DEATH_STATE_ID]

  def param_min(_)
    0
  end if Taroxd::Const[:PARAM_LIMIT]

  def param_max(_)
    Taroxd::Const::PARAM_LIMIT
  end if Taroxd::Const[:PARAM_LIMIT]

  def preserve_tp?
    true
  end if Taroxd::Const[:PRESERVE_TP]
end

class Game_Battler < Game_BattlerBase

  def luk_effect_rate(user)
    [1.0 + (user.luk - luk) * Taroxd::Const::LUK_EFFECT_RATE, 0.0].max
  end if Taroxd::Const[:LUK_EFFECT_RATE]

  def apply_critical(damage)
    damage * Taroxd::Const::CRITICAL_RATE
  end if Taroxd::Const[:CRITICAL_RATE]
end

class Game_Actor < Game_Battler

  remove_method :param_max if Taroxd::Const[:PARAM_LIMIT]

  def steps_for_turn
    Taroxd::Const::STEPS_FOR_RUN
  end if Taroxd::Const[:STEPS_FOR_RUN]

  def basic_floor_damage
    Taroxd::Const::BASIC_FLOOR_DAMAGE
  end if Taroxd::Const[:BASIC_FLOOR_DAMAGE]
end

class Game_Party < Game_Unit

  def max_battle_members
    Taroxd::Const::MAX_BATTLE_MEMBERS
  end if Taroxd::Const[:MAX_BATTLE_MEMBERS]

  def max_gold
    Taroxd::Const::MAX_GOLD
  end if Taroxd::Const[:MAX_GOLD]

  def max_item_number(_)
    Taroxd::Const::MAX_ITEM_NUMBER
  end if Taroxd::Const[:MAX_ITEM_NUMBER]
end

class Game_CharacterBase

  def update_bush_depth
    if normal_priority? && !object_character? && bush? && !jumping?
      @bush_depth = Taroxd::Const::BUSH_DEPTH unless moving?
    else
      @bush_depth = 0
    end
  end if Taroxd::Const[:BUSH_DEPTH]
end

class Sprite_Character < Sprite_Base

  def_after :initialize do |_, _ = nil|
    self.bush_opacity = Taroxd::Const::BUSH_OPACITY
  end if Taroxd::Const[:BUSH_OPACITY]
end

class Game_Player < Game_Character

  def_after :initialize do
    @direction = Taroxd::Const::PLAYER_INITIAL_DIRECTION
  end if Taroxd::Const[:PLAYER_INITIAL_DIRECTION]
end

class Scene_Battle < Scene_Base

  def check_substitute(target, item)
    target.hp_rate < Taroxd::Const::SUBSTITUTE_HP_RATE &&
      (!item || !item.certain?)
  end if Taroxd::Const[:SUBSTITUTE_HP_RATE]
end
简易调试控制台
# @taroxd metadata 1.0
# @id console
# @require taroxd_core
# @require fast_forward
# @display 简易调试控制台
# 感谢 Fux2 提供的 gets 修复脚本。

if $TEST

module Taroxd::Console

  KEY = :F5
  
  HOOK_STDIN = true   # 使得在新版控制台中也可以输入

  exit_help = 'exit 为真时,返回游戏。'

# @help
  HELP = <<-EOF.gsub(/^ {4}/, '')

    在控制台中可以执行任意脚本。下面是一些预定义的方法。

    exit
      退出控制台并返回游戏。

    help
      显示这段帮助。
      
    _ (变量)
      上一次执行的结果。

    recover(exit = true)
      完全恢复。#{exit_help}

    save(index = 0, exit = true)
      存档到指定位置。#{exit_help}

    load(index = 0, exit = true)
      从指定位置读档。#{exit_help}

    kill(hp = 0, exit = true)
      将敌方全体的 HP 设为 hp。仅战斗中可用。#{exit_help}

    suicide(hp = 0, exit = true)
      将己方全体的 HP 设为 hp。#{exit_help}

    fast_forward(*args)
      调用 Taroxd::FastForward。

    ff(*args)
      调用 Taroxd::FastForward 并返回游戏。

  EOF

  class << self

    EXIT_IDENTIFIER = Object.new   # 返回该值时,退出控制台并回到游戏

    # 获取窗口句柄
    console = Win32API.new('Kernel32', 'GetConsoleWindow', '', 'L').call
    game = Win32API.new('user32', 'GetActiveWindow', '', 'L').call
    hwnd = game
    set_window_pos = Win32API.new('user32', 'SetWindowPos', 'LLLLLLL', 'L')

    # 切换窗口
    define_method :switch_window do
      hwnd = hwnd == game ? console : game
      set_window_pos.call(hwnd, 0, 0, 0, 0, 0, 3)
    end

    # 如果按下按键,则进入控制台
    def update
      start if Input.trigger?(KEY)
    end

    alias_method :get_binding, :binding

    # 进入控制台
    def start
      switch_window
      binding = get_binding
      begin
        while (line = gets)
          next unless line[/\S/]
          _ = eval(line, binding)
          if _.equal?(EXIT_IDENTIFIER)
            switch_window
            Input.update    # 防止按下的 Enter 被游戏判定
            break
          end
          print '=> '
          p _
        end
      rescue => e
        p e
        retry
      end
    end

    def exit
      EXIT_IDENTIFIER
    end

    def help
      puts HELP
    end

    def recover(to_exit = true)
      $game_party.recover_all
      !to_exit || exit
    end

    def save(index = 0, to_exit = true)
      Sound.play_save
      DataManager.save_game_without_rescue(index)
      !to_exit || exit
    end

    def load(index = 0, to_exit = true)
      DataManager.load_game_without_rescue(index)
      Sound.play_load
      $game_system.on_after_load
      SceneManager.goto(Scene_Map)
      !to_exit || exit
    end

    def kill(hp = 0, to_exit = true)
      return to_exit && exit unless $game_party.in_battle
      $game_troop.each { |a| a.hp = hp }
      !to_exit || exit
    end

    def suicide(hp = 0, to_exit = true)
      $game_party.each { |a| a.hp = hp }
      !to_exit || exit
    end

    define_method :fast_forward, Taroxd::FastForward

    def ff(*args)
      fast_forward(*args)
      exit
    end
  end
  
  module ReadFileHooker
    ReadProcessMemory = Win32API.new('kernel32', 'ReadProcessMemory', 'llpll', 'l')
    WriteProcessMemory = Win32API.new('kernel32', 'WriteProcessMemory', 'llpll', 'l')
    VirtualProtect = Win32API.new('kernel32', 'VirtualProtect', 'lllp', 'l')
    GetModuleHandle = Win32API.new('kernel32', 'GetModuleHandle', 'p', 'l')
    GetProcAddress = Win32API.new('kernel32', 'GetProcAddress', 'lp' , 'l')
    GetCurrentProcess = Win32API.new('kernel32', 'GetCurrentProcess', 'v', 'l')

    def self.hook
      hook_addr = CAD - PROC - 5
      writemem(CAD + CAL - 6, @origin_code_readfile, 6)
      writemem(PROC, [0xE9, hook_addr, 0x90].pack("ClC"), 6)
      yield
    ensure
      writemem(PROC, @origin_code_readfile, 6)
    end
    
    class << ::Taroxd::Console
      def gets
        ReadFileHooker.hook { STDIN.gets }
      end
    end

    private

    def self.readmem(addr, buf, len)
      ReadProcessMemory.call(@hProc, addr, buf, len, 0)
    end
 
    def self.writemem(addr, buf, len)
      WriteProcessMemory.call(@hProc, addr, buf, len, 0)
    end
 
    def self.unprotect(addr, len)
      VirtualProtect.call(addr, len, 0x40, "\0" * 4)
    end
 
    def self.getmodule(name)
      GetModuleHandle.call(name)
    end
 
    def self.getaddr(dll, name)
      GetProcAddress.call(dll, name)
    end
 
    @hProc = GetCurrentProcess.call
    raise "cannot open process" if @hProc == 0
    
    HookCode = [0xC7,0x44,0x24,0x0C,0x12,0x05,0x00,0x00,*[0]*6].pack("C*")
    CAD = [HookCode].pack("p").unpack("L").first
    CAL = HookCode.bytes.count
    dll = getmodule("kernel32")
    PROC = getaddr(dll, "ReadFile")
    
    @origin_code_readfile = "\0" * 6
    readmem(PROC, @origin_code_readfile, 6)
    unprotect(CAD, CAL)
  end if HOOK_STDIN
end

Scene_Base.send :def_after, :update, Taroxd::Console.method(:update)

end # if $TEST
全局配置管理器
# @taroxd metadata 1.0
# @display 全局配置管理器
# @id config_manager
module Taroxd
  module ConfigManager
    SAVEFILE_NAME = 'config.rvdata2'

    if File.exist?(SAVEFILE_NAME)
      @data = load_data(SAVEFILE_NAME)
    else
      @data = {}
    end

    def self.[](key)
      @data[key]
    end

    def self.[]=(key, value)
      @data[key] = value
      on_change
    end

    def self.on_change
      save_data @data, SAVEFILE_NAME
    end

    # This method can be used for consecutive value changes,
    # so that the file is written only once.
    # The methods defined in Hash can be used for data manipulation.
    def self.data
      if block_given?
        begin
          yield @data
        ensure
          on_change
        end
      else
        @data
      end
    end
  end
end