Envision, Create, Share

Welcome to HBGames, a leading amateur game development forum and Discord server. All are welcome, and amongst our ranks you will find experts in their field from all aspects of video game design and development.

Vancian Spellcasting

Vancian Spellcasting
By Pidey

Version .8

Introduction:
This is a system designed to place a special restriction on spellcasters.  This system causes the player to have to decide beforehand what spells he wants.

Features:
  • Forces players to prepare spells before they are cast.
  • Ability to mark some characters as vancian, while others remain spontaneous.
  • Allows a character to "specialize" in a specific spell for increased effect.

Screenshots:
http://img258.imageshack.us/img258/5351/vancianlx5.th.png[/img]


Script:
Code:
#==============================================================================
# ** Window_Vancian
#------------------------------------------------------------------------------
#  This window displays usable skills on the Vancian preparation menu
#Vancian version .8
# Written by Pidey
#==============================================================================

class Window_Vancian < Window_Selectable
  #--------------------------------------------------------------------------
  # * Object Initialization
  #     actor : actor
  #--------------------------------------------------------------------------
  def initialize(actor)
    super(0, 128, 640, 352)
    @actor = actor
    @column_max = 2
    refresh
    self.index = 0
  end
  #--------------------------------------------------------------------------
  # * Acquiring Skill
  #--------------------------------------------------------------------------
  def skill
    return @data[self.index]
  end
  
  def showIndex
    return self.index
  end
  #--------------------------------------------------------------------------
  # * Refresh
  #--------------------------------------------------------------------------
  def refresh
    if self.contents != nil
      self.contents.dispose
      self.contents = nil
    end
    @data = []
    for i in 0...@actor.skills.size
      skill = $data_skills[@actor.skills[i]]
      if skill != nil
        @data.push(skill)
      end
    end
    # If item count is not 0, make a bit map and draw all items
    @item_max = @data.size
    if @item_max > 0
      self.contents = Bitmap.new(width - 32, row_max * 32)
      self.contents.font.name = $fontface
      self.contents.font.size = $fontsize
      for i in 0...@item_max
        draw_item(i)
      end
    end
  end
  #--------------------------------------------------------------------------
  # * Draw Item
  #     index : item number
  #--------------------------------------------------------------------------
  def draw_item(index)
    skill = @data[index]
    if @actor.skill_has_sp?(skill.id)
      self.contents.font.color = normal_color
    else
      self.contents.font.color = disabled_color
    end
    x = 4 + index % 2 * 320
    y = index / 2 * 32
    rect = Rect.new(x, y, self.width / @column_max - 32, 32)
    self.contents.fill_rect(rect, Color.new(0, 0, 0, 0))
    bitmap = RPG::Cache.icon(skill.icon_name)
    opacity = self.contents.font.color == normal_color ? 255 : 128
    self.contents.blt(x, y + 4, bitmap, Rect.new(0, 0, 24, 24), opacity)
    self.contents.draw_text(x + 28, y, 180, 32, skill.name, 0)
    self.contents.draw_text(x + 190, y, 32, 32, skill.sp_cost.to_s, 2)
    self.contents.draw_text(x + 226, y, 32, 32, @actor.vancian[skill.id].to_s, 2) #this displays how many times the spell is prepared.
  end
  #--------------------------------------------------------------------------
  # * Help Text Update
  #--------------------------------------------------------------------------
  def update_help
    @help_window.set_text(self.skill == nil ? "" : self.skill.description)
  end
end
Code:
#==============================================================================
# ** Window_Vancian_aid
#------------------------------------------------------------------------------
#  This window gives options during the vancian preparation.
#Vancian version .8
# Written by Pidey
#==============================================================================

class Window_Vancian_aid < Window_Selectable
  #--------------------------------------------------------------------------
  # * Object Initialization
  #--------------------------------------------------------------------------
  def initialize
    super(0, 0, 336, 480)
    self.contents = Bitmap.new(width - 32, height - 32)
    self.contents.font.name = $fontface
    self.contents.font.size = $fontsize
    self.z += 10
    @item_max = 4
    refresh
  end
  #--------------------------------------------------------------------------
  # * Refresh
  #--------------------------------------------------------------------------
  def refresh
    self.contents.clear
    for i in 0...$game_party.actors.size
      x = 4
      y = i * 116

      self.contents.draw_text(4, 0, 324, 68, "Set as Default")
      self.contents.draw_text(4, 116, 224, 48, "Clear one of spell")
      self.contents.draw_text(4, 232, 224, 48, "Clear all of spell")
      self.contents.draw_text(4, 348, 224, 48,  "Clear all memory")
      actor = $game_party.actors[i]
    end
  end
  #--------------------------------------------------------------------------
  # * Cursor Rectangle Update
  #--------------------------------------------------------------------------
  def update_cursor_rect
    # Cursor position -1 = all choices, -2 or lower = independent choice
    # (meaning the user's own choice)
    if @index <= -2
      self.cursor_rect.set(0, (@index + 10) * 116, self.width - 32, 96)
    elsif @index == -1
      self.cursor_rect.set(0, 0, self.width - 32, @item_max * 116 - 20)
    else
      self.cursor_rect.set(0, @index * 116, self.width - 32, 96)
    end
  end
end
Code:
#-----------------------------------------------------------------
#Vancian
#-----------------------------------------------------------------
#This class is used to hold a character's Vancian Memory,
#As well as several important functions to the Vancian's working.
#Vancian version .8
# Written by Pidey

class Vancian
  #this class holds a character's vancian memory.
  
  def initialize
    @current_memory = []
    @prefered_memory = []
    incriment = 1
    while $data_skills[incriment] != nil  #this fills the memory with zeroes, so there aren't any nil
      @prefered_memory[incriment] = 0
      @current_memory[incriment] = 0
      incriment += 1
    end
  end
  
  def ensure_prefered_capacity  #use this to catch errors in the event of maximum SP lowering.
    if prefered_memory_cost <=  $game_actors[@char].max_sp
      return true
    else
      return false
    end
  end
  
  
  def set_as_default #this copies the current memory into prefered memory for later retrieval.
    incriment = 1
    while $data_skills[incriment] != nil
      @prefered_memory[incriment] = @current_memory[incriment]
      incriment += 1
    end
  end
    
  
  def prefered_memory_cost #this returns the total SP cost of prefered spells
    incriment = 1
    total = 0
    while $data_skills[incriment] != nil
      total += $data_skills[incriment].sp_cost * @prefered_memory[incriment]
      incriment += 1
    end
    return total
  end
  
  def current_total  #this returns the total SP cost of all currently memorized spells
    incriment = 1
    total = 0
    while $data_skills[incriment] != nil
      total += $data_skills[incriment].sp_cost * @current_memory[incriment]
      incriment += 1
    end
    return total
  end
  
  
  def set_prefered  #sets memory as prefered, and returns total cost
    incriment = 1
    total = 0
    while $data_skills[incriment] != nil
      @current_memory[incriment] = @prefered_memory[incriment]
      total+= $data_skills[incriment].sp_cost * @current_memory[incriment]
      incriment +=1
    end
    return total
  end
  
  def [](line_number)
      return @current_memory[line_number]
  end
    
  def []=(switch_id, value)
    if switch_id <= 5000
      @current_memory[switch_id] = value
    end
  end
  
  def clear_memory  #warning!  Clears memory WITHOUT restoring maxMP.
    incriment = 1
    while $data_skills[incriment] != nil
      @current_memory[incriment] = 0
      incriment += 1
    end
  end
end
Code:
#==============================================================================
# ** Scene_Vancian
#------------------------------------------------------------------------------
#  This Class is the Vancian preparation screen
#Vancian version .8
# Written by Pidey
#==============================================================================

class Scene_Vancian
  
  def initialize(char)
    @char = char
    if not $game_party.actors[@char].is_vancian
      $scene = Scene_Menu.new(6)
      return
    else
      @memory = $game_party.actors[@char].vancian
    end
  end

  def main
    @actor = $game_party.actors[@char]
    @help_window = Window_Help.new
    @status_window = Window_SkillStatus.new(@actor)
    @vancian_window = Window_Vancian.new(@actor)
    @vancian_window.help_window = @help_window
    @aid_window = Window_Vancian_aid.new
    @aid_window.visible = false
    Graphics.transition
    loop do
      Graphics.update
      Input.update
      update
      if $scene != self
        break
      end
    end
    Graphics.freeze
    @help_window.dispose
    @status_window.dispose
    @vancian_window.dispose
  end

  
  def update
    # Update windows
    @help_window.update
    @status_window.update
    @vancian_window.update
    # If skill window is active: call update_skill
    if @vancian_window.active
      update_vancian
      return
    end
    # If skill target is active: call update_target
    if @aid_window.active
      update_aid
      return
    end
  end
  
  
  def update_vancian
    unless $game_party.actors[@char].is_vancian
      $scene = Scene_Menu.new(6)
    end
    # Pressing B returns to menu
    if Input.trigger?(Input::B)
      $game_system.se_play($data_system.cancel_se)
      $scene = Scene_Menu.new(6)
      return
    end
    
    if Input.trigger?(Input::A)
      $game_system.se_play($data_system.cursor_se)
      @vancian_window.active = false
      temp = @vancian_window.index
      @aid_window.x = (temp +1) % 2 * 304
      @aid_window.visible = true
      @aid_window.active = true
      @aid_window.index = 0
    end
    
    # If C button was pressed
    if Input.trigger?(Input::C)
      # Get currently selected data on the skill window
      @skill = @vancian_window.skill
      # If unable to use
      if @skill == nil or  @actor.sp <@skill.sp_cost
        # Play buzzer SE
        $game_system.se_play($data_system.buzzer_se)
        return
      end
      # Play decision SE
      $game_system.se_play($data_system.decision_se)
      @memory[@skill.id] +=1
      @actor.sp -= @skill.sp_cost  #Preparing a vancian spell lowers SP
      @actor.maxsp -= @skill.sp_cost
      @vancian_window.refresh
      @status_window.refresh
      return
    end
  end
  
  def update_aid
    @aid_window.update
    if Input.trigger?(Input::B)
      $game_system.se_play($data_system.cancel_se)
      @vancian_window.active = true
      @aid_window.visible = false
      @aid_window.active = false
      return
    end
    if Input.trigger?(Input::C)  #REMOVE
      case @aid_window.index
      when 0 #set as default
        @actor.vancian.set_as_default
        $game_system.se_play($data_system.decision_se)
      when 1 #clear one of spell
        if @actor.vancian[@vancian_window.skill.id] > 0
          @actor.vancian[@vancian_window.skill.id] -=1
          @actor.maxsp += @vancian_window.skill.sp_cost
          @actor.sp += @vancian_window.skill.sp_cost / 4
          $game_system.se_play($data_system.decision_se)
        else
          $game_system.se_play($data_system.cancel_se)
        end
      when 2 #clear all of spell
        if @actor.vancian[@vancian_window.skill.id] > 0
          total = @vancian_window.skill.sp_cost * @actor.vancian[@vancian_window.skill.id]
          @actor.maxsp += total
          @actor.sp += total / 3
          $game_system.se_play($data_system.decision_se)
          @actor.vancian[@vancian_window.skill.id] = 0
        else
          $game_system.se_play($data_system.cancel_se)
        end
      when 3 #clear all memory
        total = @actor.vancian.current_total
        @actor.maxsp += total
        @actor.sp = @actor.sp +  total /2
        @actor.vancian.clear_memory
        $game_system.se_play($data_system.decision_se)
      end
      @vancian_window.refresh
      @status_window.refresh
    end
  end
  
end
Code:
# This contains misc things needed for vancian spellcasting.
#Vancian version .8
# Written by Pidey



class Game_Actor < Game_Battler
  alias old_setup setup
  def setup(actor_id)
    @vancian = Vancian.new
    if actor_id == 7 or actor_id == 8  #Specify who is vancian here!
      @is_vancian = true
    else
      @is_vancian = false
    end
    old_setup(actor_id)
  end
end



class Game_Battler
  attr_reader   :is_vancian
  attr_reader   :vancian
  alias recover_all_old recover_all
  def recover_all
    if is_vancian == true
      @maxsp_plus += vancian.current_total
      @maxsp_plus = [[@maxsp_plus, -9999].max, 9999].min
      vancian.clear_memory
    end
    @hp = maxhp
    @sp = maxsp
    if is_vancian == true
      if vancian.prefered_memory_cost <= maxsp
        t = vancian.set_prefered
        @maxsp_plus -= t
        @maxsp_plus = [[@maxsp_plus, -9999].max, 9999].min
        @sp -= t
      end
    end
    for i in @states.clone
      remove_state(i)
    end
  end
  
  alias skill_can_use_old? skill_can_use?
  def skill_can_use?(skill_id)
    # If there's not enough SP, or preparation the skill cannot be used.
    if self.is_vancian == true
      if self.vancian[skill_id] == 0
        return false
      end
    else
      if $data_skills[skill_id].sp_cost > self.sp
        return false
      end
    end
    # Unusable if incapacitated
    if dead?
      return false
    end
    # If silent, only physical skills can be used
    if $data_skills[skill_id].atk_f == 0 and self.restriction == 1
      return false
    end
    # Get usable time
    occasion = $data_skills[skill_id].occasion
    # If in battle
    if $game_temp.in_battle
      # Usable with [Normal] and [Only Battle]
      return (occasion == 0 or occasion == 1)
    # If not in battle
    else
      # Usable with [Normal] and [Only Menu]
      return (occasion == 0 or occasion == 2)
    end
  end
  
  def skill_has_sp?(skill_id)
    if $data_skills[skill_id].sp_cost > self.sp
      return false
    end
    return true
  end
  
  alias skill_effect_old skill_effect
  def skill_effect(user, skill)
    self.critical = false
    if ((skill.scope == 3 or skill.scope == 4) and self.hp == 0) or
       ((skill.scope == 5 or skill.scope == 6) and self.hp >= 1)
      return false
    end
    effective = false
    effective |= skill.common_event_id > 0
    hit = skill.hit
    if skill.atk_f > 0
      hit *= user.hit / 100
    end
    hit_result = (rand(100) < hit)
    effective |= hit < 100
    if hit_result == true
      power = skill.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
      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)
      if user.is_vancian == true #Increases the power of a spell for preparing it repeatedly.
        vancian_boost = user.vancian[skill.id] * 0.05 +1
        vancian_boost = vancian_boost ** 1.15
      else
        vancian_boost = 1
      end
      self.damage = (power * rate / 20) * vancian_boost 
      self.damage *= elements_correct(skill.element_set)
      self.damage /= 100
      if self.damage > 0
        if self.guarding?
          self.damage /= 2
        end
      end
      self.damage = self.damage.round
      if skill.variance > 0 and self.damage.abs > 0
        amp = [self.damage.abs * skill.variance / 100, 1].max
        self.damage += rand(amp+1) + rand(amp+1) - amp
      end
      eva = 8 * self.agi / user.dex + self.eva
      hit = self.damage < 0 ? 100 : 100 - eva * skill.eva_f / 100
      hit = self.cant_evade? ? 100 : hit
      hit_result = (rand(100) < hit)
      effective |= hit < 100
    end
    if hit_result == true
      if skill.power != 0 and skill.atk_f > 0
        remove_states_shock
        effective = true
      end
      last_hp = self.hp
      self.hp -= self.damage
      effective |= self.hp != last_hp
      @state_changed = false
      effective |= states_plus(skill.plus_state_set)
      effective |= states_minus(skill.minus_state_set)
      if skill.power == 0
        self.damage = ""
        unless @state_changed
          self.damage = "Miss"
        end
      end
    else
      self.damage = "Miss"
    end
    unless $game_temp.in_battle
      self.damage = nil
    end
    return effective
  end
end

class Window_Skill < Window_Selectable
  alias draw_item_old draw_item
  def draw_item(index)
    skill = @data[index]
    if @actor.skill_can_use?(skill.id)
      self.contents.font.color = normal_color
    else
      self.contents.font.color = disabled_color
    end
    x = 4 + index % 2 * (288 + 32)
    y = index / 2 * 32
    rect = Rect.new(x, y, self.width / @column_max - 32, 32)
    self.contents.fill_rect(rect, Color.new(0, 0, 0, 0))
    bitmap = RPG::Cache.icon(skill.icon_name)
    opacity = self.contents.font.color == normal_color ? 255 : 128
    if @actor.is_vancian == true
      self.contents.draw_text(x + 28, y, 180, 32, skill.name, 0)
      self.contents.draw_text(x + 190, y, 32, 32, skill.sp_cost.to_s, 2)
      self.contents.draw_text(x + 226, y, 32, 32, @actor.vancian[skill.id].to_s, 2)
    else
      self.contents.blt(x, y + 4, bitmap, Rect.new(0, 0, 24, 24), opacity)
      self.contents.draw_text(x + 28, y, 204, 32, skill.name, 0)
      self.contents.draw_text(x + 232, y, 48, 32, skill.sp_cost.to_s, 2)
    end
  end
end


class Scene_Battle
  alias make_skill_action_result_old make_skill_action_result
  def make_skill_action_result
    @skill = $data_skills[@active_battler.current_action.skill_id]
    unless @active_battler.current_action.forcing
      unless @active_battler.skill_can_use?(@skill.id)
        $game_temp.forcing_battler = nil
        @phase4_step = 1
        return
      end
    end
    if @active_battler.is_vancian == true
      @active_battler.vancian[@skill.id] -= 1
      @active_battler.maxsp += @skill.sp_cost
    else
      @active_battler.sp-= @skill.sp_cost
    end
    @status_window.refresh
    @help_window.set_text(@skill.name, 1)
    @animation1_id = @skill.animation1_id
    @animation2_id = @skill.animation2_id
    @common_event_id = @skill.common_event_id
    set_target_battlers(@skill.scope)
    for target in @target_battlers
      target.skill_effect(@active_battler, @skill)
    end
  end
end

Instructions:
Simply drop the scripts above main in your game, then near the top of the Misc_Vancian part of the script, modify the one line to mark characters to use the vancian system.  Then use $scene = Scene_Vancian.new(character's party position) to enter the preparation screen

Demo:
http://hosted.filefront.com/lordpidey  Download Vancian.zip  (note that this requires RTP)
http://www.mediafire.com/?ce9c0itcczd

Notes:
This script probably could work with RMVX with a little work.  However I do not own a copy of RMVX.
 
Cool, thats awesome. I never thought it would be possible to emulate that sort of casting from 3.5 D&D, since defining ability scores and having spells per day dependent on class seems a lot of effort to script. Its a nice idea, tieing it to max Sp instead. And neat trick on the SP reduction as spells are memorised. Also gives you an actual reason to specialise. (I noticed Hilda did lots of damage with Wind when I'd pumped her full of 8 copies of it)

Shame I dumped the old D&D game_battler I made. (the one that used rand(6) to roll D6 and such like).

I assume one can call the memorise scene without using the game menu. If I were to use this, I'd also tie it to only being able to memorise spells when camping or at an inn (as it should be).

EDIT: Technically speaking, 4th Ed D&D does have a minor aspect of Vancian casting still in there, but only for Wizards. You know more daily powers than you can use a day and simply select some you can use. A 1st level character can, for example, know both Acid Arrow and Flaming Sphere in their spellbook, but only picks one to use per day. At least 1st level wizards aren't complete and utter pansies now (woot, I don't have the same HP as a commoner, and thankyou, limitless magic missiles!)
 

Khoa

Member

Thanks for uploading on MerdiaFire.com (^^)
I tried the demo, it's very cool, I'll use it in my game (^^)
But can you update this script so player gets a buzzer when trying to make non-mage access Vancian Spellcasting? (not fade-in then fade-out)
 
eharper256":1en6a80u said:
Also gives you an actual reason to specialise. (I noticed Hilda did lots of damage with Wind when I'd pumped her full of 8 copies of it)

I assume one can call the memorise scene without using the game menu. If I were to use this, I'd also tie it to only being able to memorise spells when camping or at an inn (as it should be).

1.) Yes, there is a bonus to spells the more it is prepared.  The effect is multiplied by  (1+.05*times prepared)^1.15

2.) Yes, simply call $scene = Scene_Vancian.new(character's party position)

3.) Also, re-read the 3.5 D&D rules.  If a wizard deliberately leaves a slot open during his morning preparation, he can fill the slot later in the day.


Khoa":1en6a80u said:
But can you update this script so player gets a buzzer when trying to make non-mage access Vancian Spellcasting? (not fade-in then fade-out)

That was simply the menu that I hastily coded.  You will have to use $scene = Scene_Vancian.new(party member's position) to enter the menu.


I've also uploaded a slightly updated demo, which fixed that issue, however the vancian system was not modified at all.
 

Thank you for viewing

HBGames is a leading amateur video game development forum and Discord server open to all ability levels. Feel free to have a nosey around!

Discord

Join our growing and active Discord server to discuss all aspects of game making in a relaxed environment. Join Us

Content

  • Our Games
  • Games in Development
  • Emoji by Twemoji.
    Top