Code:
# * KSkillRunes XP
#   Scripter : Kyonides Arkanthes
#   2021-04-03 - Beta 9
# This script is a Rune Based Skill Learning System vaguely similar to a
# Materia System.
# * Instructions * #
# Create Items representing the Rune Skill Gem Stones.
# Create a Temp directory and place the template TXT file here and set its
# values accordingly. Copy and Paste it into new TXT files as many times as
# needed.
# Set the ITEM_IDS_RANGE constant's value below.
# If a Hero has acquired a Rune Gem Stone, no existing skills will be
# overwritten. Every Rune Gem Stone has an activation cost (in gold pieces).
# Every Rune Skill needs Rune Points so spend them wisely.
# Game_Battler#skill_effect has been overwritten.
# * Script Calls * #
# * Open Rune Scene - Positions: 0 through 3
# $scene = KSkillRuneScene.new
# $scene = KSkillRuneScene.new(ActorIndex)
# * First Select an Actor - Positions: 0 through 3
# actor = $game_party.actors[Position]
# * Set or Increase or Decrease the Maximum Number of the Actor's Skill Runes
# actor.skill_runes_max = Number
# actor.skill_runes_max += Number
# actor.skill_runes_max -= Number
# * Manually Increase an Actor's RP (Rune Points)
# actor.rp += Number
# * Use Rune Points RP to Activate a given Skill the Rune can teach the Hero
# actor.learn_rune_skill(RuneItemID, TargetSkillID)
module KSRunes
  TEMP_DIR = "Temp"
  MISSED_TARGET = "Miss"
  TITLE = 'Skill Runes'
  STARTING_RUNE_MAX = 1
  ITEM_IDS_RANGE = 40..51
  BACKDROP = ''
  ACTOR_BLOCKED_RUNES = {}
# * End of Setup Section * #
  class Skill
    attr_accessor :id, :level, :learn_skill, :skill_points
    def can_learn?(pts) @skill_points == pts end
  end
  class Rune
    def initialize
      @id = 0
      @name = ""
      @level = 0
      @level_max = 1
      @blocked_rune = 0
      @price = 0
      @skills = {}
    end
    attr_accessor :id, :name, :level, :level_max, :blocked_rune, :price, :skills
  end
  extend self
  attr_reader :runes
  def scan_int(line) line.scan(/\d+/).map{|d| d.to_i } end
  def parse_skill(numbers)
    skill = Skill.new
    skill.id = numbers.shift
    skill.level = numbers.shift
    skill.learn_skill = numbers.shift
    skill.skill_points = numbers.shift
    skill
  end
  def parse_file(name)
    rune = Rune.new
    lines = File.readlines(name)
    rune.id = scan_int(lines.shift)[0]
    rune.name = lines.shift.sub(/magic type: /i, "")
    rune.level = scan_int(lines.shift)[0]
    rune.level_max = scan_int(lines.shift)[0]
    rune.blocked_rune = scan_int(lines.shift)[0] || 0
    rune.price = scan_int(lines.shift)[0]
    n = 0
    lines.each do |line|
      numbers = scan_int(line)
      skill = parse_skill(numbers)
      rune.skills[skill.id] = skill
    end
    @runes[rune.id] = rune
  end
  def retrieve_runes
    return @runes = load_data('Data/KSkillRunes.rxdata') unless $DEBUG
    @runes = {}
    Dir[TEMP_DIR + '/*.txt'].sort.each{|name| parse_file(name) }
    File.open('Data/KSkillRunes.rxdata', 'wb'){|f| Marshal.dump(@runes, f) }
  end
  retrieve_runes
end
class Game_Battler
  def alive_not_dead?(skill)
    (skill.scope == 3 or skill.scope == 4) and @hp == 0
  end
  def dead_not_alive?(skill)
    (skill.scope == 5 or skill.scope == 6) and @hp >= 1
  end
  def calculate_skill_power
    power = @skill.power
    if @user.is_actor? and @user.rune_skill?(@skill.id)
      rune_skill = @user.rune_skills[@skill.id]
      power += @skill.variance * rune_skill.level
    end
    power = power + @user.atk * @skill.atk_f / 100
    if power > 0
      power -= self.pdef * @skill.pdef_f / 200
      power -= self.mdef * @skill.mdef_f / 200
      power = [power, 0].max
    end
    power
  end
  def checking_hit(hit, hit_result)
    return hit unless hit_result
    power = calculate_skill_power
    rate = 20
    rate += (@user.str * @skill.str_f / 100)
    rate += (@user.dex * @skill.dex_f / 100)
    rate += (@user.agi * @skill.agi_f / 100)
    rate += (@user.int * @skill.int_f / 100)
    @damage = power * rate / 20
    @damage *= elements_correct(@skill.element_set)
    @damage /= 100
    @damage /= 2 if @damage > 0 and self.guarding?
    if @skill.variance > 0 and @damage.abs > 0
      amp = [@damage.abs * @skill.variance / 100, 1].max
      @damage += rand(amp+1) + rand(amp+1) - amp
    end
    eva = 8 * self.agi / @user.dex + self.eva
    hit = @damage < 0 ? 100 : 100 - eva * @skill.eva_f / 100
    self.cant_evade? ? 100 : hit
  end
  def checking_effectivity(effective)
    if @skill.power != 0 and @skill.atk_f > 0
      remove_states_shock
      effective = true
    end
    last_hp = @hp
    self.hp -= @damage
    effective |= @hp != last_hp
    @state_changed = false
    effective |= states_plus(@skill.plus_state_set)
    effective |= states_minus(@skill.minus_state_set)
    @last_damage = @damage
    if @skill.power == 0
      @damage = @state_changed ? "" : KSRunes::MISSED_TARGET
      @last_damage = 0
    end
    effective
  end
  def skill_effect(user, skill)
    @critical = false
    user.last_skill = $game_temp.in_battle ? skill : nil
    return false if alive_not_dead?(skill) or dead_not_alive?(skill)
    @user = user
    @skill = skill
    effective = false
    effective |= @skill.common_event_id > 0
    hit = @skill.hit
    hit *= @user.hit / 100 if @skill.atk_f > 0
    hit_result = (rand(100) < hit)
    effective |= hit < 100
    hit = checking_hit(hit, hit_result)
    effective |= hit < 100
    hit_result = (rand(100) < hit)
    if hit_result
      effective = checking_effectivity(effective)
    else
      @damage = "Miss"
      @last_damage = 0
    end
    unless $game_temp.in_battle
      @damage = nil
      @last_damage = 0
    end
    @skill = @user = nil
    return effective
  end
  attr_accessor :last_skill, :last_damage
end
class Game_Actor
  alias :kyon_skillrunes_gm_actor_init :initialize
  def initialize(actor_id)
    kyon_skillrunes_gm_actor_init(actor_id)
    @skill_runes_max = KSRunes::STARTING_RUNE_MAX
    @rune_skills = {}
    @rp = 0
  end
  
  def item_effect(item)
    item_id = item.id
    return super(item) unless KSRunes::ITEM_IDS_RANGE.include?(item_id)
    return false unless learn_from_rune?(item_id)
    return false if rune_skill?(item_id)
    return false if skill_runes_max?
    rune = KSRunes.runes[item_id]
    return false if $game_party.gold < rune.price
    $game_party.gain_gold(-rune.price)
    skills = rune.skills
    learn_rune_skills(rune.blocked_rune, skills.values)
    skills.each{|k,v| @rune_skills[k] ||= v.dup }
  end
  def learn_rune_skill(rid, sid)
    rskills = @rune_skills[rid]
    return "No Rune" unless rskills
    skill = rskills[sid]
    return "Wrong Skill ID" unless skill
    return "Not Enough RP" if @rp == 0 or skill.skill_points > @rp
    skill.level += 1 if skill.level < KSRunes.runes[rid].level_max
    @rp -= skill.skill_points
  end
  def learn_from_rune?(rune_id)
    blocked_runes = KSRunes::ACTOR_BLOCKED_RUNES[@actor_id]
    return true unless blocked_runes
    !blocked_runes.include?(rune_id)
  end
  def learn_rune_skill?(level, skill_id, skill)
    return false if @level < level or skill_learn?(skill_id)
    skill.can_learn?(@rp)
  end
  def learn_rune_skills(rune_id, blocked_rune, skills)
    br = blocked_rune
    skills = skills.map{|s| s.id if learn_rune_skill?(s.level, br, s) }
    skills = skills.compact
    @skills = (@skills + skills).uniq.sort
  end
  def rune_skill?(skill_id) @rune_skills.has_key?(skill_id) end
  def skill_runes_max?() @rune_skills.size == @skill_runes_max end
  def is_actor?() true end
  def runes() @rune_skills.keys.sort end
  attr_accessor :skill_runes_max, :rp
  attr_reader :rune_skills
end
class Game_Enemy
  def is_actor?() false end
end
class KSRuneSpriteset
  include KSRunes
  def initialize(pos)
    @index = pos
    @rune_index = 0
    @actors = $game_party.actors
    actor = @actors[@index]
    @backdrop = Sprite.new
    @backdrop.bitmap = RPG::Cache.title(BACKDROP)
    @heading = Sprite.new
    @heading.x = 160
    @heading.y = 8
    @heading.bitmap = b = Bitmap.new(320, 32)
    b.draw_text(0, 4, 320, 24, TITLE, 1)
    @name_sprite = Sprite.new
    @name_sprite.x = 8
    @name_sprite.y = 36
    @name_sprite.bitmap = b = Bitmap.new(240, 32)
    b.draw_text(0, 4, 240, 24, actor.name, 1)
    @icon_names = actor.runes.map{|n| $data_items[n].icon_name }
    @icons = []
    @icon_labels = []
    @icon_names.size.times{|n| draw_rune_icons(n) }
    @sprites = [@backdrop, @heading, @name_sprite]
  end
  def draw_rune_icons(pos)
    @icons << s = Sprite.new
    s.x = 8
    s.y = 64 + pos * 28
    s.bitmap = RPG::Cache.icon(@icon_names[pos])
    @icon_labels << l = Sprite.new
    l.x = s.x + 28
    l.y = s.y
    l.bitmap = b = Bitmap.new(200, 24)
    b.draw_text(0, 0, 200, 24, @icon_names[pos])
  end
  def dispose
    @icon_labels.each{|s| s.dispose }
    @sprites.each{|s| s.dispose }
    @icons.each{|s| s.dispose }
    @sprites.clear
    @icons.clear
    @icon_names.clear
    @actors = nil
  end
  attr_reader :rune_index
end
class KSkillRuneScene
  def initialize(pos=0) @index = pos end
  def main
    @run = true
    @spriteset = KSRuneSpriteset.new(@index)
    Graphics.transition
    while @run
      Graphics.update
      Input.update
      update
    end
    Graphics.freeze
    @spriteset.dispose
  end
  def update
    if Input.trigger?(Input::B)
      $game_system.se_play($data_system.cancel_se)
      $scene = Scene_Map.new
      return @run = nil
    elsif Input.trigger?(Input::C)
      $game_system.se_play($data_system.decision_se)
      
      
    end
  end
end