RPG MAKER XP - Save file problem

kR1pt0n1t3

Veteran
Veteran
Joined
Dec 24, 2014
Messages
53
Reaction score
7
First Language
Croatian
Primarily Uses
RMMV
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.



This is the script it's referring to inside the error that I didn't change at all...
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
 

KK20

Just some XP Scripter
Veteran
Joined
Oct 11, 2018
Messages
281
Reaction score
106
First Language
English
Primarily Uses
RMXP
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).
 

Shaz

Veteran
Veteran
Joined
Mar 2, 2012
Messages
40,098
Reaction score
13,704
First Language
English
Primarily Uses
RMMV
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.
 

kR1pt0n1t3

Veteran
Veteran
Joined
Dec 24, 2014
Messages
53
Reaction score
7
First Language
Croatian
Primarily Uses
RMMV
@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.
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
 
Last edited:

Users Who Are Viewing This Thread (Users: 0, Guests: 1)

Latest Threads

Latest Profile Posts

People3_5 and People3_8 added!

so hopefully tomorrow i get to go home from the hospital i've been here for 5 days already and it's driving me mad. I miss my family like crazy but at least I get to use my own toiletries and my own clothes. My mom is coming to visit soon i can't wait to see her cause i miss her the most. :kaojoy:
Couple hours of work. Might use in my game as a secret find or something. Not sure. Fancy though no? :D
Holy stink, where have I been? Well, I started my temporary job this week. So less time to spend on game design... :(
Cartoonier cloud cover that better fits the art style, as well as (slightly) improved blending/fading... fading clouds when there are larger patterns is still somewhat abrupt for some reason.

Forum statistics

Threads
105,868
Messages
1,017,078
Members
137,580
Latest member
Snavi
Top