#==============================================================================
# ■ 数据库通用备注接口 v2.1 by SailCat
#------------------------------------------------------------------------------
#   方法:本脚本插入到Main之前使用,且需在所有依赖此脚本的其他插件之前
#   依赖:无
#   版本:v2.1 (Build 171217)
#   效果:给所有数据库项目增加如同VA、MV一样的备注,且备注长度同VA、MV一样无限
#   配置:有大量配置项,见脚本配置区的具体说明,按默认设定可以正常运行
#   冲突:其他的数据库扩展脚本
#   说明:
#
#   1. 备注字段来源:
#      游戏事件页、公共事件数据使用注释(第1条起的连续内容)作为备注(无限)
#      队伍数据使用注释(第1页[不论条件]第1条起的连续内容)作为备注(无限)
#      物品、特技、武器、防具数据,用描述作为备注(最多100字)
#      角色、职业、敌人、状态、动画、图块数据,用名称作为备注(最多40字)
#      地图、事件数据,用名称作为备注(最多100字)
#      所有数据,皆可以引用公共事件中的标签或文件作为备注,因此理论上长度无限
#      用于批量存放备注数据的公共事件编号和文件名,可以在脚本配置中指定
#
#   2. 备注字段写法:
#      标准写法1:#备注项1;备注项2;...备注项n 
#      标准写法2:#>公共事件中的超长备注标签名
#      标准写法3:#<配置文件中的超长备注标签名
#      以上标准写法互相排斥,根据需要任选一种
#      符号# (半角井号)用于区分备注与描述/名称正文,以及识别注释事件
#      符号; (半角分号)用于给备注项分段
#      符号#>用于整体引用公共事件中的超长备注标签
#      符号#<用于整体引用配置文件中的超长备注标签
#      上述分隔符可在下方脚本配置中,修改为其他字符
#      非标准写法:#任意备注内容,此种情况下,备注不会自动解析为备注项集的形式
#
#   3. 备注项的写法:
#      标准写法:标识符=表达式
#      由于直接书写空间有限,当表达式为某些特殊情况时,本脚本支持以下简写:
#      当表达式的值为true时,可写作 标识符 或者 标识符+
#      当表达式的值为false时,可写作 标识符-
#      当表达式的值为字符串时,可写作 标识符:字符串
#      当表达式的值为超长备注标签的引用字符串时,可写作 标识符.标签名
#      表达式中可用v 指代游戏变量,如v[8]相当于$game_variables[8]
#      表达式中包括备注分段符号(默认为;)时,用前置反斜杠转义
#
#   4. 标准写法备注的使用:
#      脚本中调用备注项,使用 数据对象._+标识符(默认值) 方法
#      符号_ 为标识符前缀,区分同原有字段的偶然重名,锁定不可修改
#      若调用的标识符无效或未定义,将返回默认值,默认值省略的话返回nil
#      强烈建议在植入备注功能时,用好记的写法和默认值,对标识符进行二次引用定义
#    调用示例:
#      角色1  名称设为:阿尔西斯 #a;b=4;c:5
#      则  $data_actors[1].name       # => "阿尔西斯" 自动区隔备注内容
#          $data_actors[1]._a         # => true
#          $data_actors[1]._b         # => 4
#          $data_actors[1]._c         # => "5"
#          $data_actors[1]._d         # => nil 未定义的标识符,采用nil
#          $data_actors[1]._d(2)      # => 2   未定义的标识符,采用默认值
#      技能1  描述设为:回复己方单体HP。#f:a.atk - b*def / 2;cd=1.5;no_ref-
#      则  $data_skills[1].description# => "回复己方单体HP。"
#          $data_skills[1]._f         # => "a.atk - b.def / 2"
#          $data_skills[1]._cd        # => 1.5
#          $data_skills[1]._no_ref    # => false
#    二次定义示例(采用默认值和好记写法,方便他处脚本的书写,并增强可读性):
#      module RPG
#        class Skill
#          def formula;  _f;       end
#          def cooldown; _cd(0.0); end
#        end
#      end
#
#   5. 非标准写法备注的使用:数据对象.note 方法可取得备注内容(字符串),以上
#
#   6. 狂野模式:
#      如果在脚本配置中将相关选项打开,可以使用狂野模式的备注
#      此时,若标识符写作该类数据的默认字段名称(只能是数值),原定数值会被覆盖
#      该方法可以轻易突破数据库的数值限制,如负数武器攻击力,超过200的能力F值
#      该方法可能破坏数据库的自洽性,因此请谨慎使用该功能
#
#   7. 本脚本只提供数据库内容的备注接口,没有植入关于备注项实装的任何扩展功能
#      备注项的实际应用代码,请自行实现或插入后续发布的依赖插件
#      脚本作者只承诺发布的所有基于本接口的依赖插件之间不会互相冲突
#      如果与非依赖本接口的插件或他人插件冲突,请自行解决
#
#   8. 关于用公共事件实现超长备注:
#      1)公共事件的超长备注,用事件指令书写在指定编号的公共事件中
#         公共事件的编号在脚本配置区指定,默认分开12项,也可合并为1项
#         超长备注可用“显示文章”或“注释”或“脚本”指令书写,具体指令无区别
#         仅提取这三条指令的文字内容,其他的指令会被自动忽略,不会有影响
#      2) 建议在公共事件最开始插入“中断事件处理”指令,避免误执行(不是必须)
#      3)每条超长备注之前,加上标签指令,标签名不得互相重复
#      4)超长备注可以作为备注项的字符串使用,例:
#           阿尔西斯 #pr.c1 # => 将标签c1后文字作为$data_actors[1]._pr的内容
#         也可以作为备注项数据,整体合使用,例:
#           阿尔西斯 #>c1   # => 将标签c1后文字作为阿尔西斯的备注,并自动解析
#         整体使用时,写在公共事件中的备注项需省略前置的#符号(因为已经写过了)
#      5) 不是用配置中的公共事件ID存放时,标签名需指定公共事件ID,如:#>99,c1
#      6) 标签名可以省略不写,用数据的名称和相关标识符作为默认标签名,如:
#           阿尔西斯 #>   # => 系统会查找名为“阿尔西斯”的标签并取值
#           阿尔西斯 #pr. # => 系统会查找名为“阿尔西斯_pr”的标签并取值
#      7) 引用的标签没有定义的情况下,返回空的字符串
#
#   9. 关于备注文件:
#      1) 备注文件的写法须为标准的INI配置文件写法,即标签分段,键值对书写,如:
#           [特萝西]
#           profile=这是特萝西,她是盗贼
#           age=28
#           gender=女
#           height=175
#           weight=60
#           is_dual_wield=false
#      2) 数据库引用的写法为:#<标签名,当标签名与数据名称一样时可以省略,如:
#           特萝西 #<
#         则有:$data_actors[4]._profile="这是特萝西,她是盗贼" 等等
#==============================================================================
#==============================================================================
# ■ SailCat's 插件公用
#==============================================================================
module SailCat
  $sailcat_import ||= {}
  #--------------------------------------------------------------------------
  # ● 脚本配置区
  #--------------------------------------------------------------------------
  module DataNoteCore_Config
    SEPARATOR = "#"               # 备注与正文的分隔符
    DELIMITER = ";"               # 分隔不同备注项的符号
    NOTE_LABEL_CHAR = ?>          # 超长备注(公共事件存储)的识别符
    NOTE_FILE_CHAR = ?<           # 超长备注(文件存储)的识别符
    NOTE_FILE = "Data/Note.ini"   # 超长备注(文件存储)的文件名
    ACTOR_COMMON_EVENT_ID = 1     # 超长备注(角色类)用公共事件ID
    CLASS_COMMON_EVENT_ID = 2     # 超长备注(职业类)用公共事件ID
    SKILL_COMMON_EVENT_ID = 3     # 超长备注(特技类)用公共事件ID
    ITEM_COMMON_EVENT_ID = 4      # 超长备注(物品类)用公共事件ID
    WEAPON_COMMON_EVENT_ID = 5    # 超长备注(武器类)用公共事件ID
    ARMOR_COMMON_EVENT_ID = 6     # 超长备注(防具类)用公共事件ID
    ENEMY_COMMON_EVENT_ID = 7     # 超长备注(敌人类)用公共事件ID
    STATE_COMMON_EVENT_ID = 8     # 超长备注(状态类)用公共事件ID
    ANIMATION_COMMON_EVENT_ID = 9 # 超长备注(动画类)用公共事件ID
    TILESET_COMMON_EVENT_ID = 10  # 超长备注(图块类)用公共事件ID
    MAPINFO_COMMON_EVENT_ID = 11  # 超长备注(地图类)用公共事件ID
    EVENT_COMMON_EVENT_ID = 12    # 超长备注(事件类)用公共事件ID
    NOTE_STRING_LF = "\n"         # 超长备注(字符串引用)的行分隔符号
    NOTE_DATA_LF = DELIMITER      # 超长备注(整体引用)的行分隔符号
    USE_WILD_MODE = true          # 是否开启狂野模式
    #--------------------------------------------------------------------------
    # ● 配置检查,请勿更改
    #--------------------------------------------------------------------------
    raise "备注分隔符与分项分隔符不得一致" if SEPARATOR == DELIMITER
    raise "公共事件标识与文件标识不得一致" if NOTE_LABEL_CHAR == NOTE_FILE_CHAR
  end
  #--------------------------------------------------------------------------
  # ● 植入
  #--------------------------------------------------------------------------
  $sailcat_import[:DataNoteCore] = 2.1
end
 
#==============================================================================
# ■ DataNoteCore
#------------------------------------------------------------------------------
#   数据库内容通用备注接口核心引擎
#==============================================================================
module DataNoteCore
  include SailCat::DataNoteCore_Config
  #--------------------------------------------------------------------------
  # ● 常量
  #--------------------------------------------------------------------------
  SEP_REGEX = /#{SEPARATOR}.+$/  # 切分用正则表达式,请不要修改
  #--------------------------------------------------------------------------
  # ● 获取备注字段
  #--------------------------------------------------------------------------
  def note
    # 如果备注值没有初始化
    if @note.nil?
      # 如果是公共事件,用执行列表前面的注释
      @note = split(get_command_note(list)) if self.is_a?(RPG::CommonEvent)
      # 如果是游戏事件,用执行列表前面的注释
      @note = split(get_command_note(list)) if self.is_a?(Game_Event)
      # 如果是队伍,用第1页执行列表前面的注释
      @note = split(get_command_note(pages[0].list)) if self.is_a?(RPG::Troop)
      # 如果是其他,用描述或者名称来备注
      @note ||= has_description? ? split(@description) : split(@name)
      # 解析备注字符串
      analyze
    end
    # 返回值
    return @note
  end
  #--------------------------------------------------------------------------
  # ● 设置备注字段(运行时改变)
  #      note: 新的备注
  #--------------------------------------------------------------------------
  def note=(note)
    # 设置新值
    @note = note
    # 重新解析备注
    analyze
  end
  #--------------------------------------------------------------------------
  # ● 取得/设置备注值(泛用)
  #--------------------------------------------------------------------------
  def method_missing(param_name, *args, &block)
    return super unless respond_to?(param_name)
    param_str = param_name.to_s.sub!(/^_/, "")
    # 备注赋值的情况下
    if param_str[-1] == 61
      param_key = param_str.chop
      self.class.send :define_method, param_name do |value|
        set_note(param_key, value)
      end
      set_note(param_key, *args)
    # 备注取值的情况下
    else
      self.class.send :define_method, param_name do |value|
        get_note(param_str, value)
      end
      return get_note(param_str, *args)
    end
  end
  #--------------------------------------------------------------------------
  # ● 是否有描述字段(内部使用)
  #--------------------------------------------------------------------------
  def has_description?
    instance_variables.include?("@description")
  end
  #--------------------------------------------------------------------------
  # ● 从事件列表中切取备注(内部使用)
  #--------------------------------------------------------------------------
  def get_command_note(list)
    for index in 0...list.length
      break if list[index].code != 108 and list[index].code != 408
    end
    join_command_note(list[0...index], DELIMITER)
  end
  #--------------------------------------------------------------------------
  # ● 将事件内容转为备注字符串(内部使用)
  #--------------------------------------------------------------------------
  def join_command_note(list, linefeed)
    (list.collect {|x| x.parameters[0]}).join(linefeed)
  end
  #--------------------------------------------------------------------------
  # ● 切分备注用字段(内部使用)
  #--------------------------------------------------------------------------
  def split(text)
    text.slice(SEP_REGEX).to_s
  end
  #--------------------------------------------------------------------------
  # ● 取得备注参数值(内部使用)
  #--------------------------------------------------------------------------
  def get_note(param_name, default = nil)
    return default if self.note == "" or not @note_set.include?(param_name)
    return @note_set[param_name] == nil ? default : @note_set[param_name]
  end
  #--------------------------------------------------------------------------
  # ● 设置备注参数值(内部使用)
  #--------------------------------------------------------------------------
  def set_note(param_name, value)
    @note_set ||= {}
    @note_set[param_name] = value
  end
  #--------------------------------------------------------------------------
  # ● 获取标签备注值(内部使用)
  #--------------------------------------------------------------------------
  def get_label(label, identifier, linefeed)
    # 返回已设置的缓存值
    event_id = label.sub!(/^([0-9]+),/, "") ? $1.to_i : note_event_id
    label = default_label(identifier) if label == ""
    $data_notes ||= {}
    if $data_notes.has_key?([event_id, label])
      return $data_notes[[event_id, label]]
    end
    # 初始化公共事件
    $data_common_events ||= load_data("Data/CommonEvents.rxdata")
    event = $data_common_events[event_id]
    return "" if event.nil? or event.list.length == 1
    # 检索标签
    list = event.list
    start_index = 0
    end_index = list.length
    for index in 0...list.length
      next if list[index].code != 118
      if list[index].parameters[0] == label
        start_index = index + 1
      elsif start_index > 0
        break end_index = index
      end
    end
    # 检索结果
    if start_index == 0
      result = ""
    else
      cmds = list[start_index...end_index]
      cmds.reject! {|x| not [101, 401, 108, 408, 355, 655].include?(x.code)}
      result = join_command_note(cmds, linefeed)
    end
    $data_notes[[event_id, label]] = result
  end
  #--------------------------------------------------------------------------
  # ● 获取文件备注值(内部使用)
  #--------------------------------------------------------------------------
  def read_ini_file(label)
    return unless FileTest.exist?(NOTE_FILE)
    # 初始化标签
    label = default_label(nil) if label == ""
    # 打开备注文件
    File.open(NOTE_FILE, "r") do |f|
      label_key = "[#{label}]\n"
      found = false
      # 循环每一行
      f.each_line do |l|
        # 注释的情况下,继续
        next if l[0] == ?; or l[0] == ?#
        # 标签不符的情况下,继续
        next unless l == label_key or found
        # 跳过下一行
        (found = true; next) unless found
        # 如果已到下一个标签就中断
        break if l[0] == ?[
        # 获得键值对
        pair = l.split("=", 2)
        next if pair.size < 2
        key = pair[0].strip
        value = pair[1].strip
        # 值运行成功为表达式,失败默认为字符串
        result = lambda{|v| eval(value) rescue value}.call($game_variables)
        # 设置备注值
        @note_set[key] = result
      end
    end
  end
  #--------------------------------------------------------------------------
  # ● 获取超长标签的公共事件ID(内部使用)
  #--------------------------------------------------------------------------
  def note_event_id
    class_name = self.class.to_s[5..-1]
    eval(class_name.upcase + "_COMMON_EVENT_ID") rescue 0
  end
  #--------------------------------------------------------------------------
  # ● 获取超长标签默认名称(内部使用)
  #--------------------------------------------------------------------------
  def default_label(id)
    if self.is_a?(RPG::Event)
      sprintf("%03d#%s%s", $game_map.map_id, self.name, id ? "_#{id}" : "")
    else
      self.name + (id ? "_#{id}" : "")
    end
  end
  #--------------------------------------------------------------------------
  # ● 解析备注字符串(该方法每对象通常只会执行一次,除非运行时整体重设备注)
  #--------------------------------------------------------------------------
  def analyze
    @note_set = {}
    # 如果备注为空则返回
    return if @note == nil or @note == ""
    # 如果备注为标签引用
    if @note[1] == NOTE_LABEL_CHAR
      @note = SEPARATOR + get_label(@note[2..-1], nil, NOTE_DATA_LF)
    elsif @note[1] == NOTE_FILE_CHAR
      read_ini_file(@note[2..-1])
      return
    end
    # 解析备注字符串
    param_regex = /^ *([A-Za-z0-9_]+)\b([+:=.\-]?(.*?))$/
    # 转义分隔符
    note_array = @note[1..-1].gsub(/\\#{DELIMITER}/, "\001").split(DELIMITER)
    note_array.each do |x|
      result = nil
      if x[param_regex] != nil
        param_name = $1
        param_value = $2
        param_exp = $3.gsub(/\001/, DELIMITER)
        # 开关型(+)
        if param_value.strip == "" or param_value[0] == ?+
          result = true
        # 开关型(-)
        elsif param_value[0] == ?-
          result = false
        # 数值型
        elsif param_value[0] == ?=
          result = lambda{|v| eval(param_exp) rescue nil}.call($game_variables)
        # 字符型
        elsif param_value[0] == ?:
          result = param_exp
        # 标签型
        elsif param_value[0] == ?.
          result = get_label(param_exp, param_name, NOTE_STRING_LF)
        end
        @note_set[param_name] = result
      end
    end
  end
  #--------------------------------------------------------------------------
  # ● 判断方法名是否合法
  #--------------------------------------------------------------------------
  def respond_to?(method_name, *)
    method_name.to_s[/^_.+/] == nil ? super : true
  end
  #--------------------------------------------------------------------------
  # ● 狂野模式覆盖注入
  #--------------------------------------------------------------------------
  def define_wild_methods
    return unless USE_WILD_MODE
    v = instance_variables.select {|i| instance_variable_get(i).is_a?(Numeric)}
    v.each do |i|
      next if i == "@id"
      var = i[1..-1]
      get_method = var.to_sym; wild_get = "_#{var}".to_sym
      set_method = "#{var}=".to_sym; wild_set = "_#{var}=".to_sym
      self.class.send :define_method, get_method do
        send wild_get, instance_variable_get(i)
      end
      self.class.send :define_method, set_method do |value|
        send wild_set, value
        instance_variable_set(i, value)
      end
    end
  end
  #--------------------------------------------------------------------------
  # ● 工具方法:扁平化数组
  #--------------------------------------------------------------------------
  def flat_array(param)
    param.to_a.map {|f| f.to_a}.flatten
  end
end
 
#==============================================================================
# ■ RPG 模块的植入
#------------------------------------------------------------------------------
#   以下植入14个数据库内容模块(含地图数据和事件数据)
#==============================================================================
module RPG
  [Actor, Class, Enemy, State, Animation, Tileset, MapInfo, Event].each do |c|
    c.send :include, DataNoteCore
    c.send :define_method, :name do 
      @name1 ||= (@name.sub(DataNoteCore::SEP_REGEX) {|s| ""}).strip
    end
    c.send :define_method, :name= do |value|
      @name1 = value.strip
      @name = @name1 + DataNoteCore::SEPARATOR + note
    end
    c.new.define_wild_methods unless c == RPG::Event
  end
  [Item, Skill, Weapon, Armor].each do |c|
    c.send :include, DataNoteCore
    c.send :define_method, :description do
      @description1 ||= (@description.sub(DataNoteCore::SEP_REGEX) {|s| ""}).strip
    end
    c.send :define_method, :description= do |value|
      @description1 = value.strip
      @description = @description1 + DataNoteCore::SEPARATOR + note
    end
    c.new.define_wild_methods
  end
  [Troop, CommonEvent].each do |c|
    c.send :include, DataNoteCore
  end
end
 
#==============================================================================
# ■ Game_Event
#==============================================================================
class Game_Event
  include DataNoteCore
end