RPG MAKER XP - Save file problem

● ARCHIVED · READ-ONLY
Started by kR1pt0n1t3 4 posts View original ↗
  1. I have a problem that I don't know how to solve.

    The idea:
    - After the XY event ends in RPG Maker, call Save Scene and overwrite a save file but only update $game_switches in that file. Other things inside the file leave as it is. After that is done, go to the title screen of the game.

    The purpose of this is to allow the player to replay endings after he's seen them. Since all the ending events end with the player going to the title screen of the game, I need to save somewhere information that the player has seen that event before that happens. For that purpose, I made it so XY switch is flipped before the player is transferred to the title screen, and Save Scene is called so that the switch I flipped is saved inside the latest save file.

    Ideally, I would've liked it somehow all existing save files would be automatically edited and the switch change saved inside them, but I don't even know where to begin.

    Anyhow, I ended up messing around and came up with a way to do it on just one file. But, it's not working as I planned... The solution is probably something stupid but I can't figure it out.

    --------------------------
    MY THOUGHT PROCESS
    --------------------------

    There are two functions that I need to adjust to being able to pull this off.

    1ST FUNCTION
    Code:
    def on_decision(filename)
        $game_system.se_play($data_system.save_se)
        file = File.open(filename, "wb")
        write_save_data(file)
        file.close
        if $game_temp.save_calling
           $game_temp.save_calling = false
           $scene = Scene_Map.new
           return
        end
          $scene = Scene_Menu.new(4)
    end

    2ND FUNCTION
    Code:
    def write_save_data(file)
          characters = []
          for i in 0...$game_party.actors.size
            actor = $game_party.actors[i]
            characters.push([actor.character_name, actor.character_hue])
          end
          Marshal.dump(characters, file)
          Marshal.dump(Graphics.frame_count, file)
          $game_system.save_count += 1
          $game_system.magic_number = $data_system.magic_number
          Marshal.dump($game_system, file)
          Marshal.dump($game_switches, file)
          Marshal.dump($game_variables, file)
          Marshal.dump($game_self_switches, file)
          Marshal.dump($game_screen, file)
          Marshal.dump($game_actors, file)
          Marshal.dump($game_party, file)
          Marshal.dump($game_troop, file)
          Marshal.dump($game_map, file)
          Marshal.dump($game_player, file)
    end

    So, the first thing I did was to replace the 3rd line inside the first function with all this:

    Code:
    if $game_switches[1100] == true
      file = File.open(filename, "rb+")
    else
      file = File.open(filename, "wb")
    end

    Before the Save Screen is called inside the ending event, I flipped a switch 1100 to ON so the script knows it only needs to update $game_switches inside an existing save file. To that end, I made inside the first function an if-else loop. According to the wiki or ruby, "rb+" is for when I only want to update something inside the existing file so I opened the file with "rb+" instead of "wb" that deletes everything inside the file first.

    After that I edited the 2nd function to this:
    Code:
    def write_save_data(file)
      if $game_switches[1100] == false
          characters = []
          for i in 0...$game_party.actors.size
            actor = $game_party.actors[i]
            characters.push([actor.character_name, actor.character_hue])
          end
          Marshal.dump(characters, file)
          Marshal.dump(Graphics.frame_count, file)
          $game_system.save_count += 1
          $game_system.magic_number = $data_system.magic_number
          Marshal.dump($game_system, file)
          Marshal.dump($game_variables, file)
          Marshal.dump($game_self_switches, file)
          Marshal.dump($game_screen, file)
          Marshal.dump($game_actors, file)
          Marshal.dump($game_party, file)
          Marshal.dump($game_troop, file)
          Marshal.dump($game_map, file)
          Marshal.dump($game_player, file)
      end
          Marshal.dump($game_switches, file)
    end

    I moved the "Marshal.dump($game_switches, file)" outside the IF loop so only that part gets updated in case switch 1100 is FALSE. If 1100 is TRUE(which in my case it is), everything inside the if loop will be skipped.

    Everything works exactly as I thought it would expect for this error when I try to open a save file I saved after I did these changes.

    O24i4P9.png

    This is the script it's referring to inside the error that I didn't change at all...
    Spoiler
    Code:
    =begin
    セーブ数増量(当社比)XP用
    Ver 0.0.2
    by 半生
    http://www.tktkgame.com
    
    
     セーブ可能ファイル数を増加(当社比)させます
    
     ver 0.0.3
      ・セーブ1~4がないとコンテニュー出来なかった不具合修正
     ver 0.0.2
      ・画面切り替え時にviewportを開放するように修正
    =end
    
    
    #==============================================================================
    # ■ Scene_File
    #------------------------------------------------------------------------------
    # セーブ画面およびロード画面のスーパークラスです。
    # MAX_FILE : セーブファイルの上限
    #==============================================================================
    class Scene_File
      MAX_ITEM = 40 # セーブファイルの上限
      #--------------------------------------------------------------------------
      # ● オブジェクト初期化
      #     help_text : ヘルプウィンドウに表示する文字列
      #--------------------------------------------------------------------------
      alias :_hn__initialize :initialize unless method_defined?(:_hn__initialize)
      def initialize(help_text)
        @viewport = Viewport.new(0, 64, 640, 416)
        @top_row = 0
        _hn__initialize(help_text)
      end
    
      #--------------------------------------------------------------------------
      # ● メイン処理
      #--------------------------------------------------------------------------
      # 再定義
      def main
        # ヘルプウィンドウを作成
        @help_window = Window_Help.new
        @help_window.set_text(@help_text, 1)
        # セーブファイルウィンドウを作成
        @savefile_windows = []
        for i in 0...MAX_ITEM
          @savefile_windows.push(Window_SaveFile.new(i, make_filename(i), @viewport))
        end
        # 最後に操作したファイルを選択
        self.index = $game_temp.last_file_index
        @savefile_windows[@file_index].selected = true
        # トランジション実行
        Graphics.transition
        # メインループ
        loop do
          # ゲーム画面を更新
          Graphics.update
          # 入力情報を更新
          Input.update
          # フレーム更新
          update
          # 画面が切り替わったらループを中断
          if $scene != self
            break
          end
        end
        # トランジション準備
        Graphics.freeze
        # ウィンドウを解放
        @help_window.dispose
        for i in @savefile_windows
          i.dispose
        end
        # Viewportを開放
        @viewport.dispose
      end
    
      def index=(index)
        @file_index = index % MAX_ITEM
        if @file_index < @top_row
          @top_row = @file_index
        elsif @file_index > @top_row + 3
          @top_row = [@file_index - 3, 0].max
        end
        @viewport.oy = @top_row * 104
      end
    
      #--------------------------------------------------------------------------
      # ● フレーム更新
      #--------------------------------------------------------------------------
      # 再定義
      def update
        # ウィンドウを更新
        @help_window.update
        for i in @savefile_windows
          i.update
        end
        # C ボタンが押された場合
        if Input.trigger?(Input::C)
          # メソッド on_decision (継承先で定義) を呼ぶ
          on_decision(make_filename(@file_index))
          $game_temp.last_file_index = @file_index
          return
        end
        # B ボタンが押された場合
        if Input.trigger?(Input::B)
          # メソッド on_cancel (継承先で定義) を呼ぶ
          on_cancel
          return
        end
        # 方向ボタンの下が押された場合
        if Input.repeat?(Input::DOWN)
          # 方向ボタンの下の押下状態がリピートでない場合か、
          # またはカーソル位置が 3 より前の場合
          if Input.trigger?(Input::DOWN) or @file_index < MAX_ITEM
            # カーソル SE を演奏
            $game_system.se_play($data_system.cursor_se)
            # カーソルを下に移動
            @savefile_windows[@file_index].selected = false
            self.index = (@file_index + 1) % MAX_ITEM
            @savefile_windows[@file_index].selected = true
            return
          end
        end
        # 方向ボタンの上が押された場合
        if Input.repeat?(Input::UP)
          # 方向ボタンの上の押下状態がリピートでない場合か、
          # またはカーソル位置が 0 より後ろの場合
          if Input.trigger?(Input::UP) or @file_index > 0
            # カーソル SE を演奏
            $game_system.se_play($data_system.cursor_se)
            # カーソルを上に移動
            @savefile_windows[@file_index].selected = false
            self.index = (@file_index - 1) % MAX_ITEM
            @savefile_windows[@file_index].selected = true
            return
          end
        end
      end
    end
    
    #==============================================================================
    # ■ Scene_Load
    #------------------------------------------------------------------------------
    # ロード画面の処理を行うクラスです。
    #==============================================================================
    
    class Scene_Load
      # 再定義
      def initialize
        # テンポラリオブジェクトを再作成
        $game_temp = Game_Temp.new
        # タイムスタンプが最新のファイルを選択
        $game_temp.last_file_index = 0
        latest_time = Time.at(0)
        for i in 0...Scene_File::MAX_ITEM
          filename = make_filename(i)
          if FileTest.exist?(filename)
            file = File.open(filename, "r")
            if file.mtime > latest_time
              latest_time = file.mtime
              $game_temp.last_file_index = i
            end
            file.close
          end
        end
        super("Which file do you want to load?")
      end
    end
    
    #==============================================================================
    # ■ Window_Base
    #------------------------------------------------------------------------------
    # Viewportを指定できるように拡張
    #==============================================================================
    class Window_Base
      # 再定義
      def initialize(x, y, width, height, viewport = nil)
        if viewport.is_a?(Viewport)
          super(viewport)
        else
          super()
        end
        @windowskin_name = $game_system.windowskin_name
        self.windowskin = RPG::Cache.windowskin(@windowskin_name)
        self.x = x
        self.y = y
        self.width = width
        self.height = height
        self.z = 100
      end
    end
    
    #==============================================================================
    # ■ Window_SaveFile
    #------------------------------------------------------------------------------
    # セーブ画面およびロード画面で表示する、セーブファイルのウィンドウです。
    #==============================================================================
    class Window_SaveFile
      # 再定義
      def initialize(file_index, filename, viewport = nil)
        if viewport.is_a?(Viewport)
          super(0, 64 + file_index * 104 - viewport.rect.y, 640, 104, viewport)
        else
          super(0, 64 + file_index * 104, 640, 104)
        end
      
        self.contents = Bitmap.new(width - 32, height - 32)
        @file_index = file_index
        @filename = "Save#{@file_index + 1}.rxdata"
        @time_stamp = Time.at(0)
        @file_exist = FileTest.exist?(@filename)
        if @file_exist
          file = File.open(@filename, "r")
          @time_stamp = file.mtime
          @characters = Marshal.load(file)
          @frame_count = Marshal.load(file)
          @game_system = Marshal.load(file)
          @game_switches = Marshal.load(file)
          @game_variables = Marshal.load(file)
          @total_sec = @frame_count / Graphics.frame_rate
          file.close
        end
        refresh
        @selected = false
      end
    end
    
    class Scene_Title
      #--------------------------------------------------------------------------
      # ● メイン処理
      #--------------------------------------------------------------------------
      def main
        # 戦闘テストの場合
        if $BTEST
          battle_test
          return
        end
        # データベースをロード
        $data_actors        = load_data("Data/Actors.rxdata")
        $data_classes       = load_data("Data/Classes.rxdata")
        $data_skills        = load_data("Data/Skills.rxdata")
        $data_items         = load_data("Data/Items.rxdata")
        $data_weapons       = load_data("Data/Weapons.rxdata")
        $data_armors        = load_data("Data/Armors.rxdata")
        $data_enemies       = load_data("Data/Enemies.rxdata")
        $data_troops        = load_data("Data/Troops.rxdata")
        $data_states        = load_data("Data/States.rxdata")
        $data_animations    = load_data("Data/Animations.rxdata")
        $data_tilesets      = load_data("Data/Tilesets.rxdata")
        $data_common_events = load_data("Data/CommonEvents.rxdata")
        $data_system        = load_data("Data/System.rxdata")
        # システムオブジェクトを作成
        $game_system = Game_System.new
        # タイトルグラフィックを作成
        @sprite = Sprite.new
        @sprite.bitmap = RPG::Cache.title($data_system.title_name)
        # コマンドウィンドウを作成
        s1 = "New Game"
        s2 = "Continue"
        s3 = "Game End"
        @command_window = Window_Command.new(192, [s1, s2, s3])
        @command_window.back_opacity = 160
        @command_window.x = 140 - @command_window.width / 2 - 40
        @command_window.y = 308+40
        # コンティニュー有効判定
        # セーブファイルがひとつでも存在するかどうかを調べる
        # 有効なら @continue_enabled を true、無効なら false にする
        @continue_enabled = false
        for i in 0..(Scene_File::MAX_ITEM-1)
          if FileTest.exist?("Save#{i+1}.rxdata")
            @continue_enabled = true
          end
        end
        # コンティニューが有効な場合、カーソルをコンティニューに合わせる
        # 無効な場合、コンティニューの文字をグレー表示にする
        if @continue_enabled
          @command_window.index = 1
        else
          @command_window.disable_item(1)
        end
        # タイトル BGM を演奏
        $game_system.bgm_play($data_system.title_bgm)
        # ME、BGS の演奏を停止
        Audio.me_stop
        Audio.bgs_stop
        # トランジション実行
        Graphics.transition
        # メインループ
        loop do
          # ゲーム画面を更新
          Graphics.update
          # 入力情報を更新
          Input.update
          # フレーム更新
          update
          # 画面が切り替わったらループを中断
          if $scene != self
            break
          end
        end
        # トランジション準備
        Graphics.freeze
        # コマンドウィンドウを解放
        @command_window.dispose
        # タイトルグラフィックを解放
        @sprite.bitmap.dispose
        @sprite.dispose
      end
    end
  2. The problem is you're loading data back into the game in the wrong order.
    The default scripts (and the rewritten one you provided) assumes this order
    Code:
          file = File.open(@filename, "r")
          @time_stamp = file.mtime
          @characters = Marshal.load(file)
          @frame_count = Marshal.load(file)
          @game_system = Marshal.load(file)
          @game_switches = Marshal.load(file)
          @game_variables = Marshal.load(file)
          @total_sec = @frame_count / Graphics.frame_rate
          file.close
    When switch ID 1100 is on, your save file has the game switches saved at the top of the save file. So @characters is being set equal to your Game_Switches, which means @frame_count is being set equal to the characters, hence the error message.

    What you would want to do instead is re-load the save file but put the data into temporary variables. Access the temporary variable storing game switches, make the changes necessary to it, and dump the temporary variables back into the save file. Something along the lines of this (assumes this method is in some subclass of Scene_File)
    Code:
    def update_switches_in_save(save_id)
      filename = make_filename(save_id)
    
      # Load the save file's data
      file = File.open(filename, "rb")
      characters    = Marshal.load(file)
      frame_count   = Marshal.load(file)
      system        = Marshal.load(file)
      switches      = Marshal.load(file)
      variables     = Marshal.load(file)
      self_switches = Marshal.load(file)
      screen        = Marshal.load(file)
      actors        = Marshal.load(file)
      party         = Marshal.load(file)
      troop         = Marshal.load(file)
      map           = Marshal.load(file)
      player        = Marshal.load(file)
      file.close
    
      # Make the changes to game switches
      switches = $game_switches
    
      # Dump back into save file
      file = File.open(filename, "wb")
      Marshal.dump(characters, file)
      Marshal.dump(frame_count, file)
      Marshal.dump(system, file)
      Marshal.dump(switches, file)
      Marshal.dump(variables, file)
      Marshal.dump(self_switches, file)
      Marshal.dump(screen, file)
      Marshal.dump(actors, file)
      Marshal.dump(party, file)
      Marshal.dump(troop, file)
      Marshal.dump(map, file)
      Marshal.dump(player, file)
      file.close
    end
    Run above code for every save file in the project.

    Alternatively, make a separate save file that only holds data whether an ending is unlocked or not. Then any save file can access this, even new games. This is definitely the easier and safer way (personally recommend this route).
  3. You have a problem with your logic.

    If I use switches 1-10 for 10 different game endings, and the player has already completed ending 2, switch 2 will be on in the save file. If they then play a new game and complete ending 7, switch 7 will be on in the current game, but switch 2 will be off. So if I read in the "save" info for the game endings, then replace all the switches with my current game's switches, then save again, the game ending save info will have switch 7 turned on, but not switch 2. So only one ending will be recorded - the most recent one.

    It would be easier to create a completely separate file for saving the ending info, and only update the one ending each time.
  4. @KK20
    Thanks, I didn't know the order was important.
    I think I got everything figured out now.

    First I'll make a for loop from 1 to 40 to check if a save file exists. Then, if it exists, I'll load the data from the file, update the switches and save it back to that file just like you suggested. That way all the existing save files will be updated. Still got two questions.

    1. How resources heavy is this? What if a player has all 40 saves.
    2. When I load a save file data, will the current switches in the game be replaced from the ones from the save file? I imagine they will and they should, but I'm not 100% sure.


    @Shaz
    They won't be starting a new game. This is a pretty long RPG game so no one will start from scratch and lose 5-6 hours of progress just because they saw one end.

    EDIT:
    I think this should do the trick. What do you think?

    Code:
    def on_decision(filename)
        # セーブ SE を演奏
        $game_system.se_play($data_system.save_se)
        # セーブデータの書き込み
     
        if $game_variables[1025] != 0
        #temp variable so game knows which ending player triggered and what switch needs to be updated.
            for i in 0...40
                filename = make_filename(i)
                if FileTest.exist?(filename)
                    update_switches_in_save(i)
                end
            end
        else
            file = File.open(filename, "wb")
            write_save_data(file)
            file.close
            # イベントから呼び出されている場合
            if $game_temp.save_calling
              # セーブ呼び出しフラグをクリア
              $game_temp.save_calling = false
              # マップ画面に切り替え
              $scene = Scene_Map.new
              return
            end
            # メニュー画面に切り替え
            $scene = Scene_Menu.new(4)
        end
      end

    Code:
      def update_switches_in_save(save_id)
        filename = make_filename(save_id) #this is probably redundant
        file = File.open(filename, "rb")
        characters      = Marshal.load(file)
        frame_count     = Marshal.load(file)
        system          = Marshal.load(file)
        $game_switches  = Marshal.load(file)
        variables       = Marshal.load(file)
        self_switches   = Marshal.load(file)
        screen          = Marshal.load(file)
        actors          = Marshal.load(file)
        party           = Marshal.load(file)
        troop           = Marshal.load(file)
        map             = Marshal.load(file)
        player          = Marshal.load(file)
        file.close
     
        if $game_variables[1025] == 1 #ending 1
            $game_switches[1026] = true
        elsif $game_variables[1025] == 2 #ending 2
            $game_switches[1027] = true
        elsif $game_variables[1025] == 3 #ending 3
            $game_switches[1028] = true
        elsif $game_variables[1025] == 4 #ending 4
            $game_switches[1029] = true
        elsif $game_variables[1025] == 5 #ending 5
            $game_switches[1030] = true
        end
     
        file = File.open(filename, "wb")
        Marshal.dump(characters, file)
        Marshal.dump(frame_count, file)
        Marshal.dump(system, file)
        Marshal.dump($game_switches, file)
        Marshal.dump(variables, file)
        Marshal.dump(self_switches, file)
        Marshal.dump(screen, file)
        Marshal.dump(actors, file)
        Marshal.dump(party, file)
        Marshal.dump(troop, file)
        Marshal.dump(map, file)
        Marshal.dump(player, file)
        file.close
        end
      end

    I'll probably make a separate function and change the code a bit more so I don't need to call save scene. Basically, the player won't even know anything happened, he will just be sent to the title screen after he sees the ending.

    EDIT2:
    I just tested it out and it's working exactly as I wanted it to work.
    Thanks again KK20.

    EDIT3:
    Just made a separate class which you can call with "@instance_class = Save_Update.new" inside event script and it works like a charm. In case someone in the future needs something like this I'll post it here.
    Spoiler
    Code:
    class Save_Update < Scene_File
    
      def initialize
          update_save_files
      end
    
      def update_save_files
        if $game_variables[1025] != 0
          for i in 0...40
          filename = make_filename(i)
            if FileTest.exist?(filename)
              update_switches_in_save(filename)
            end
          end
        end
      end
      #--------------------------------------------------------------------------
      # ● Update save file
      #--------------------------------------------------------------------------
      def update_switches_in_save(filename)
        file = File.open(filename, "rb")
        new_characters = Marshal.load(file)
        new_frame_count = Marshal.load(file)
        new_system = Marshal.load(file)
        $game_switches = Marshal.load(file)
        new_variables = Marshal.load(file)
        new_self_switches = Marshal.load(file)
        new_screen = Marshal.load(file)
        new_actors = Marshal.load(file)
        new_party = Marshal.load(file)
        new_troop = Marshal.load(file)
        new_map = Marshal.load(file)
        new_player = Marshal.load(file)
        file.close
       
        if $game_variables[1025] == 1 #ending 1
          $game_switches[1026] = true
        elsif $game_variables[1025] == 2 #ending 2
          $game_switches[1027] = true
        elsif $game_variables[1025] == 3 #ending 3
          $game_switches[1028] = true
        elsif $game_variables[1025] == 4 #ending 4
          $game_switches[1029] = true
        elsif $game_variables[1025] == 5 #ending 5
          $game_switches[1030] = true
        end
       
        file = File.open(filename, "wb")
        Marshal.dump(new_characters, file)
        Marshal.dump(new_frame_count, file)
        Marshal.dump(new_system, file)
        Marshal.dump($game_switches, file)
        Marshal.dump(new_variables, file)
        Marshal.dump(new_self_switches, file)
        Marshal.dump(new_screen, file)
        Marshal.dump(new_actors, file)
        Marshal.dump(new_party, file)
        Marshal.dump(new_troop, file)
        Marshal.dump(new_map, file)
        Marshal.dump(new_player, file)
        file.close
        end
    end