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.

Adding a class/module to an existing game file

I've added the Stat Distribution System by Blizzard, and the problem I'm running into is that it doesn't work with old saved game files.  When I try to load an old saved game, it gives me the error "undefined method > for Nil:NilClass"

The reason (as far as I can tell) is that any new game is created with an additional module/class which includes a variable "points" for each actor.  Does anyone know if it's possible to add this module into an existing saved game, either in the Load Game menu or otherwise?

Here's the full script.  The section "class Game_Actor < Game_Battler" is where the variable "points" is added, and I'm wondering if there's a way to "inject" that into an existing saved game.

Code:
#:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=
# Stat Distribution System by Blizzard
# Version: 1.33b
# Type: Actor Attribute Modifier
# Date: 25.3.2007
# Date v1.1b: 6.4.2007
# Date v1.2b: 22.8.2007
# Date v1.3b: 12.9.2007
# Date v1.33b: 5.11.2007
#:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=
#   
#  This work is protected by the following license:
# #----------------------------------------------------------------------------
# #  
# #  Creative Commons - Attribution-NonCommercial-ShareAlike 3.0 Unported
# #  ( http://creativecommons.org/licenses/by-nc-sa/3.0/ )
# #  
# #  You are free:
# #  
# #  to Share - to copy, distribute and transmit the work
# #  to Remix - to adapt the work
# #  
# #  Under the following conditions:
# #  
# #  Attribution. You must attribute the work in the manner specified by the
# #  author or licensor (but not in any way that suggests that they endorse you
# #  or your use of the work).
# #  
# #  Noncommercial. You may not use this work for commercial purposes.
# #  
# #  Share alike. If you alter, transform, or build upon this work, you may
# #  distribute the resulting work only under the same or similar license to
# #  this one.
# #  
# #  - For any reuse or distribution, you must make clear to others the license
# #    terms of this work. The best way to do this is with a link to this web
# #    page.
# #  
# #  - Any of the above conditions can be waived if you get permission from the
# #    copyright holder.
# #  
# #  - Nothing in this license impairs or restricts the author's moral rights.
# #  
# #----------------------------------------------------------------------------
# 
#:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=
# 
# Compatibility:
# 
#   99% compatible with SDK v1.x. 80% compatible with SDK 2.x. WILL corrupt
#   your old savegames. Might cause problems with custom leveling up systems.
#   99% compatibility with everything else.
# 
# 
# Features:
# 
#   - distribute points between different stats
#   - extra scene for point distribution with confirmation window at the end
#   - calls the "caller scene" automatically when finished
#   - add points by easily pressing RIGHT/LEFT
#   - hold Q to add 10 points at once
#   - hold W to add 100 points at once
#   - a Stat Distribution System that actually works like it should...
# 
# new in v1.1b:
#   - added option to call the Points Scene after a fight with level ups
#   - customizable icon position and opacity
# 
# new in v1.2b:
#   - improved coding and made code shorter
#   - rewritten conditions using classic syntax to avoid RGSS conditioning bug
# 
# new in v1.3b:
#   - improved coding
#   - fixed bug with AUTOMATIC_CALL after battle
#   - new AUTOMATIC_MAP_CALL works on the map as well (that means it's fully
#     compatible with Blizz-ABS)
# 
# new in v1.33b:
#   - improved coding
#   - improved compatibility
#   - fixed a little glitch
# 
# 
# Configuration:
# 
#   Set up the configuration below.
# 
#   STARTING_POINTS    - how many points should the actor have initially at
#                        level 1
#   POINTS_PER_LEVEL   - how many points should the actor gain per level
#   DISPLAY_ICON       - displays an icon on the map if ANY character in the
#                        party has any points to distribute
#   ICON_DATA          - some custom options for your icon: [X, Y, OPACITY]
#                        the default values are [612, 452, 192]
#   OWN_ICON           - use an own icon for display (the icon has to be in the
#                        Icons folder and must be named "point_notify")
#   EVASION            - the name that should be displayed for "Evasion"
#   STR_LIMIT          - max possible STR
#   DEX_LIMIT          - max possible DEX
#   AGI_LIMIT          - max possible AGI
#   INT_LIMIT          - max possible INT
#   WINDOW_MODE        - set to true to have the windows at the left, set to
#                        false to have them to at the right
#   AUTOMATIC_CALL     - set to true to have the scene called automatically
#                        after battles if at least one character got leveled up
#   AUTOMATIC_MAP_CALL - set to true to have the scene called automatically on
#                        the map if at least one character got leveled up
#                        (this works for Blizz-ABS as well), also note that
#                        this will cause the scene to called over and over as
#                        long as not all points were distributed
# 
# 
#   You can always add stat points yourself by using following syntax:
# 
#     $game_party.actors[X].add_stat_points(Z)
#     $game_actors[Y].add_stat_points(Z)
# 
#   Or you can remove them (how much sense it does is up to you...):
# 
#     $game_party.actors[X].remove_stat_points(Z)
#     $game_actors[Y].remove_stat_points(Z)
# 
#   X - position of actor in the party (STARTS FROM ZERO!)
#   Y - ID of actor in the database
#   Z - value
# 
#   You can call the Scene by using a "Call script" event command. Type into
#   the editor window this text:
# 
#     $scene = Scene_Points.new
# 
# 
# Side Note:
# 
#   Decreasing the level of an actor won't remove his gained stat points. You
#   MUST do it manually.
# 
# 
# If you find any bugs, please report them here:
# http://forum.chaos-project.com
#:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=

module BlizzCFG

#::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
# START Configuration
#::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
  STARTING_POINTS = 0
  POINTS_PER_LEVEL = 5
  DISPLAY_ICON = true
  ICON_DATA = [612, 452, 192]
  OWN_ICON = false
  EVASION = 'EVA'
  STR_LIMIT = 999
  DEX_LIMIT = 999
  AGI_LIMIT = 999
  INT_LIMIT = 999
  WINDOW_MODE = true
  AUTOMATIC_CALL = true
  AUTOMATIC_MAP_CALL = false
#::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
# END Configuration
#::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
  ATTR_LIMITS = [STR_LIMIT, DEX_LIMIT, AGI_LIMIT, INT_LIMIT]
  # ensures compatibility
  $stat_system = 1.33
  
end

#==============================================================================
# Array
#==============================================================================

class Array
  
  def sum
    result = 0
    self.each {|i| result += i if i.is_a?(Numeric)}
    return result
  end
  
end
  
#==============================================================================
# Game_Actor
#==============================================================================

class Game_Actor < Game_Battler
  
  attr_reader :points
  
  alias setup_sds_later setup
  def setup(actor_id)
    @points = BlizzCFG::STARTING_POINTS
    setup_sds_later(actor_id)
  end
  
  alias exp_sds_later exp=
  def exp=(exp)
    old_level = @level
    exp_sds_later(exp)
    add_stat_points((@level - old_level) * BlizzCFG::POINTS_PER_LEVEL)
  end
  
  def add_stat_points(val)
    @points += val if val > 0
  end
  
  def remove_stat_points(val)
    @points = [@points-val, 0].max
  end
  
end

#==============================================================================
# Window_Base
#==============================================================================

class Window_Base < Window
  
  def draw_actor_battler(actor, x, y)
    bitmap = RPG::Cache.battler(actor.battler_name, actor.battler_hue)
    cw, ch = bitmap.width, bitmap.height
    src_rect = Rect.new(0, 0, cw, ch)
    self.contents.blt(x - cw/2, y - ch/2, bitmap, src_rect)
  end
  
  alias draw_actor_parameter_sds_later draw_actor_parameter
  def draw_actor_parameter(actor, x, y, type)
    if type == 7
      self.contents.font.color = system_color
      self.contents.draw_text(x, y, 120, 32, BlizzCFG::EVASION)
      self.contents.font.color = normal_color
      self.contents.draw_text(x + 120, y, 36, 32, actor.eva.to_s, 2)
    else
      draw_actor_parameter_sds_later(actor, x, y, type)
    end
  end
  
end

#==============================================================================
# Window_Distribution_Status
#==============================================================================

class Window_Distribution_Status < Window_Base
  
  attr_accessor :actor
  
  def initialize(actor)
    super(BlizzCFG::WINDOW_MODE ? 160 : 0, 0, 480, 480)
    @actor = actor
    self.contents = Bitmap.new(width - 32, height - 32)
    if $fontface != nil
      self.contents.font.name = $fontface
      self.contents.font.size = $fontsize
    elsif $defaultfonttype != nil
      self.contents.font.name = $defaultfonttype
      self.contents.font.size = $defaultfontsize
    end
    refresh
  end
  
  def refresh
    self.contents.clear
    unless @actor == nil
      draw_actor_battler(@actor, 280, 120)
      draw_actor_name(@actor, 4, 0)
      draw_actor_class(@actor, 4, 32)
      draw_actor_level(@actor, 4, 64)
      draw_actor_state(@actor, 4, 96)
      self.contents.font.color = system_color
      self.contents.draw_text(4, 128, 80, 32, 'EXP')
      self.contents.draw_text(4, 160, 80, 32, 'next')
      self.contents.font.color = normal_color
      self.contents.draw_text(4, 128, 156, 32, @actor.exp_s, 2)
      self.contents.draw_text(4, 160, 156, 32, @actor.next_rest_exp_s, 2)
      draw_actor_hp(@actor, 4, 224, 172)
      draw_actor_sp(@actor, 4, 256, 172)
      draw_actor_parameter(@actor, 4, 320, 0)
      draw_actor_parameter(@actor, 4, 352, 1)
      draw_actor_parameter(@actor, 4, 384, 2)
      draw_actor_parameter(@actor, 4, 416, 7)
      self.contents.font.color = system_color
      self.contents.draw_text(240, 240, 96, 32, 'Equipment')
      draw_item_name($data_weapons[@actor.weapon_id], 240, 288)
      draw_item_name($data_armors[@actor.armor1_id], 240, 320)
      draw_item_name($data_armors[@actor.armor2_id], 240, 352)
      draw_item_name($data_armors[@actor.armor3_id], 240, 384)
      draw_item_name($data_armors[@actor.armor4_id], 240, 416)
    end
  end
  
end
  
#==============================================================================
# Window_Distribution
#==============================================================================

class Window_Distribution < Window_Selectable
  
  attr_accessor :actor
  attr_reader   :points
  
  def initialize(actor)
    super(BlizzCFG::WINDOW_MODE ? 0 : 480, 160, 160, 320)
    self.contents = Bitmap.new(width - 32, height - 32)
    if $fontface != nil
      self.contents.font.name = $fontface
      self.contents.font.size = $fontsize
    elsif $defaultfonttype != nil
      self.contents.font.name = $defaultfonttype
      self.contents.font.size = $defaultfontsize
    end
    self.active, self.index, = false, 0
    @item_max, @actor, @att, @points = 4, actor, [0, 0, 0, 0], 0
    refresh
  end
  
  def set_new_attributes
    @actor.str += @att[0]
    @actor.dex += @att[1]
    @actor.agi += @att[2]
    @actor.int += @att[3]
    @actor.remove_stat_points(@points)
  end
  
  def actor=(actor)
    @actor = actor
    @att[0] = @att[1] = @att[2] = @att[3] = @points = 0
  end
  
  def refresh
    self.contents.clear
    unless @actor == nil
      self.contents.font.color = system_color
      self.contents.draw_text(52, 0, 72, 32, 'DP left', 2)
      self.contents.draw_text(4, 32, 120, 32, $data_system.words.str)
      self.contents.draw_text(4, 96, 120, 32, $data_system.words.dex)
      self.contents.draw_text(4, 160, 120, 32, $data_system.words.agi)
      self.contents.draw_text(4, 224, 120, 32, $data_system.words.int)
      self.contents.font.color = normal_color
      self.contents.draw_text(4, 0, 48, 32, "#{actor.points-@points}", 2)
      self.contents.draw_text(36, 64, 56, 32, "#{@actor.str+@att[0]}", 2)
      self.contents.draw_text(36, 128, 56, 32, "#{@actor.dex+@att[1]}", 2)
      self.contents.draw_text(36, 192, 56, 32, "#{@actor.agi+@att[2]}", 2)
      self.contents.draw_text(36, 256, 56, 32, "#{@actor.int+@att[3]}", 2)
      self.contents.font.size += 8
      self.contents.font.bold = true
      (0...4).each {|i|
          self.contents.draw_text(0, (i + 1) * 64 - 8, 32, 42, '«', 2)
          self.contents.draw_text(96, (i + 1) * 64 - 8, 32, 42, '»')}
      self.contents.font.bold = false
      self.contents.font.size -= 8
    end
  end
  
  def add_points(num)
    attr = [@actor.str, @actor.dex, @actor.agi, @actor.int]
    if @points < @actor.points &&
        attr[index]+@att[index] < BlizzCFG::ATTR_LIMITS[index]
      @points = [@points + num, @actor.points].min
      @att[index] = [@att[index]+num, @points+@att[index]-@att.sum].min
      return true
    end
    return false
  end
  
  def remove_points(num)
    if @points > 0 && @att[index] > 0
      @points = [@points - num, 0].max
      @att[index] = [@att[index] - num, 0].max
      return true
    end
    return false
  end
  
  def update
    super
    return unless self.active
    if Input.press?(Input::R)
      if Input.repeat?(Input::RIGHT)
        if add_points(100)
          $game_system.se_play($data_system.cursor_se)
          refresh
        else
          $game_system.se_play($data_system.buzzer_se)
        end
      elsif Input.repeat?(Input::LEFT)
        if remove_points(100)
          $game_system.se_play($data_system.cursor_se)
          refresh
        else
          $game_system.se_play($data_system.buzzer_se)
        end
      end
    elsif Input.press?(Input::L)
      if Input.repeat?(Input::RIGHT)
        if add_points(10)
          $game_system.se_play($data_system.cursor_se)
          refresh
        else
          $game_system.se_play($data_system.buzzer_se)
        end
      elsif Input.repeat?(Input::LEFT)
        if remove_points(10)
          $game_system.se_play($data_system.cursor_se)
          refresh
        else
          $game_system.se_play($data_system.buzzer_se)
        end
      end
    elsif Input.repeat?(Input::RIGHT)
      if add_points(1)
        $game_system.se_play($data_system.cursor_se)
        refresh
      else
        $game_system.se_play($data_system.buzzer_se)
      end
    elsif Input.repeat?(Input::LEFT)
      if remove_points(1)
        $game_system.se_play($data_system.cursor_se)
        refresh
      else
        $game_system.se_play($data_system.buzzer_se)
      end
    end
  end
  
  def update_cursor_rect
    if @index < 0 || !self.active
      self.cursor_rect.empty
    else
      self.cursor_rect.set(32, (@index+1)*64, 64, 32)
    end
  end
  
end
  
#==============================================================================
# Window_Sure
#==============================================================================

class Window_Sure < Window_Command
  
  attr_accessor :actor
  
  def initialize(width, commands)
    commands.push('')
    super
    @item_max, self.index = commands.size - 1, 0
    self.x, self.y, self.z = 320-self.width/2, 240-self.height/2, 10000
    refresh
  end
  
  def refresh
    super
    self.contents.font.color = system_color
    self.contents.draw_text(4, 0, self.contents.width - 8, 32, 'Are you sure?', 1)
  end
  
  def draw_item(index, color)
    self.contents.font.color = color
    rect = Rect.new(4, 32 * (index+1), self.contents.width - 8, 32)
    self.contents.fill_rect(rect, Color.new(0, 0, 0, 0))
    self.contents.draw_text(rect, @commands[index], 1)
  end
  
  def update_cursor_rect
    if @index < 0
      self.cursor_rect.empty
    else
      self.cursor_rect.set(32, (@index+1)*32, self.contents.width - 64, 32)
    end
  end
  
end
  
#==============================================================================
# Scene_Points
#==============================================================================

class Scene_Points
  
  def initialize
    @actor, @scene = $game_party.actors[0], $scene.class
  end
  
  def main
    commands = ['Distribute', 'Next', 'Previous', 'Finish']
    @command_window = Window_Command.new(160, commands)
    @command_window.x = (BlizzCFG::WINDOW_MODE ? 0 : 480)
    @status_window = Window_Distribution_Status.new(@actor)
    @distro_window = Window_Distribution.new(@actor)
    Graphics.transition
    loop do
      Graphics.update
      Input.update
      update
      break if $scene != self
    end
    Graphics.freeze
    [@command_window, @status_window, @distro_window].each {|win| win.dispose}
  end
  
  def make_sure_window
    commands = ['Cancel', 'Accept changes', 'Discard changes']
    @sure_window = Window_Sure.new(256, commands)
  end
  
  def update
    if @command_window.active
      @command_window.update
      update_main_command
    elsif @sure_window != nil
      @sure_window.update
      update_sure
    elsif @distro_window.active
      @distro_window.update
      if Input.trigger?(Input::B)
        $game_system.se_play($data_system.cancel_se)
        @command_window.active, @distro_window.active = true, false
      end
    end
  end
  
  def update_main_command
    if Input.trigger?(Input::B)
      $game_system.se_play($data_system.cancel_se)
      if @distro_window.points != 0
        @command_window.index, @command_window.active = 3, false
        make_sure_window
      else
        $scene = @scene.new
      end
    elsif Input.trigger?(Input::C)
      $game_system.se_play($data_system.decision_se)
      case @command_window.index
      when 0 then @command_window.active, @distro_window.active = false, true
      when 1
        if @distro_window.points != 0
          @command_window.active = false
          make_sure_window
        else
          i = (@actor.index+1) % $game_party.actors.size
          @actor = @status_window.actor = @distro_window.actor = $game_party.actors[i]
          [@status_window, @distro_window].each {|win| win.refresh}
          @distro_window.index = 0
        end
      when 2
        if @distro_window.points != 0
          @command_window.active = false
          make_sure_window
        else
          i = (@actor.index+$game_party.actors.size-1) % $game_party.actors.size
          @actor = @status_window.actor = @distro_window.actor = $game_party.actors[i]
          [@status_window, @distro_window].each {|win| win.refresh}
          @distro_window.index = 0
        end
      when 3
        if @distro_window.points != 0
          @command_window.active = false
          make_sure_window
        else
          $scene = @scene.new
        end
      end
    end
  end
  
  def update_sure
    if Input.trigger?(Input::B)
      $game_system.se_play($data_system.cancel_se)
      @sure_window.dispose
      @sure_window, @command_window.active = nil, true
    elsif Input.trigger?(Input::C)
      $game_system.se_play($data_system.decision_se)
      case @command_window.index
      when 1
        if @sure_window.index > 0
          @distro_window.set_new_attributes if @sure_window.index == 1
          i = (@actor.index+1) % $game_party.actors.size
          @actor = @status_window.actor = @distro_window.actor = $game_party.actors[i]
          [@status_window, @distro_window].each {|win| win.refresh}
        end
        @sure_window.dispose
        @sure_window, @command_window.active = nil, true
      when 2
        if @sure_window.index > 0
          @distro_window.set_new_attributes if @sure_window.index == 1
          i = (@actor.index+$game_party.actors.size-1) % $game_party.actors.size
          @actor = @status_window.actor = @distro_window.actor = $game_party.actors[i]
          [@status_window, @distro_window].each {|win| win.refresh}
        end
        @sure_window.dispose
        @sure_window, @command_window.active = nil, true
      when 3
        if @sure_window.index > 0
          @distro_window.set_new_attributes if @sure_window.index == 1
          $scene = @scene.new
        end
        @sure_window.dispose
        @sure_window, @command_window.active = nil, true
      end
    end
  end
  
end

#==============================================================================
# Scene_Battle
#==============================================================================

class Scene_Battle
  
  alias main_sds_later main
  def main
    main_sds_later
    if BlizzCFG::AUTOMATIC_CALL &&
        $game_party.actors.any? {|actor| actor.points > 0}
      $game_switches[58] = 1
      $scene = Scene_Points.new
    end
  end
  
end


#==============================================================================
# Scene_Map
#==============================================================================

class Scene_Map
  
  alias main_sds_later main
  def main
    main_sds_later
    @notify.dispose if @notify != nil
  end
  
  alias upd_sds_later update
  def update
    check_icon if BlizzCFG::DISPLAY_ICON
    upd_sds_later
    #if $game_party.actors.any? {|actor| actor.points > 0}
    #  $game_switches[58] = true
    #end
  end
  
  def check_icon
    if $game_party.actors.any? {|actor| actor.points > 0}
      $game_switches[58] = true
      if @notify == nil
        @notify = RPG::Sprite.new
        if BlizzCFG::OWN_ICON
          @notify.bitmap = RPG::Cache.icon('point_notify')
        else
          @notify.bitmap = Bitmap.new(24, 24)
          @notify.bitmap.fill_rect(0, 0, 24, 24, Color.new(255, 255, 255))
          @notify.bitmap.fill_rect(22, 1, 2, 23, Color.new(0, 0, 0))
          @notify.bitmap.fill_rect(1, 22, 23, 2, Color.new(0, 0, 0))
          @notify.bitmap.set_pixel(23, 0, Color.new(0, 0, 0))
          @notify.bitmap.set_pixel(0, 23, Color.new(0, 0, 0))
          @notify.bitmap.fill_rect(2, 2, 20, 20, Color.new(0, 0, 224))
          @notify.bitmap.fill_rect(4, 10, 16, 4, Color.new(255, 255, 255))
          @notify.bitmap.fill_rect(10, 4, 4, 16, Color.new(255, 255, 255))
          @notify.opacity = BlizzCFG::ICON_DATA[2]
        end
        @notify.x, @notify.y = BlizzCFG::ICON_DATA[0, 2]
        @notify.z = 5000
        @notify.blink_on
      end
      @notify.update
    elsif @notify != nil
      @notify.dispose
      @notify = nil
    end
  end
  
end
 
I assume this is just for development purposes.

You could create another class (Old_Game_Actor), then temporarily modify the Load_Game script to read the data into the old class, copy the parameters to an object of the new class, then save using the new class.

Be Well
 

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