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.

Rataime's Image Save Files

Hi guys !

After playing Master of the Wind, and not having opened RMXP for one year, I wanted to check if I was still able to make original scripts (with an heavy dose of eye-candy, obviously). Well, you tell me. I've coded this script in a few hours today, inspired by http://www.squidi.net/three/entry.php?id=12

This script allows you to save your game in .png (picture) files. That way, you can distribute, host and share them easily, for example as a forum signature. You can also use thumbnail views for quick sorting and browsing, given that you can put whatever information you want on the picture.

For example, the following pictures :

http://rataime.free.fr/rmxp/Demos/Save1.png[/img]       http://rataime.free.fr/rmxp/Demos/Save3.png[/img]

are actual savegames of my demo. You can put them in my demo and load them in rmxp !

It's obviously up to you to configure how are generated the pictures, the ones above are just an example automatically using the battleback of the last map I was on.

The script needs the SDK, and can't be converted to non-SDK. Dealing with savefiles without the SDK is a sure way to mess with other scripts... Even now, it will still probably mess with non-SDK save scripts.

Here's the code :

Code:
#==============================================================================
# ** Image Save File
#------------------------------------------------------------------------------
# Rataime
# Version 1.0
# 11/08/2008 (2008-08-11)
# Original Idea : Squdi (http://www.squidi.net/three/entry.php?id=12)
#------------------------------------------------------------------------------
#
# This script allows you to save your game in .png (picture) files. That way,
# you can distribute and host them easily, for example as a forum signature.
# You can also use thumbnail views for quick sorting and browsing, given that
# you can put whatever innformation you want on the picture.
#
# The files will be saved as Save1.png, Save2.png... If both Save1.rxdata and
# Save1.png exist, Save1.png will be loaded.
#
# If for whatever reason you want your game to be able to load .png files but
# still saving as .rxdata, set IMAGE_SAVEFILE_SAVE_AS_PNG to false. I don't
# recommand it, as it will become complicated when there's both a Save1.rxdata 
# and Save1.png...
#
# To configure your own picture, see rataime_generate_png_file_from_template
#
#==============================================================================

#============================================================================== 
# IMAGE_SAVEFILE_SAVE_AS_PNG : Set to false to save as .rxdata
#==============================================================================

IMAGE_SAVEFILE_SAVE_AS_PNG = true

class Scene_Save < Scene_File
  
  #--------------------------------------------------------------------------
  # * rataime_generate_png_file_from_template
  #
  # This method generates the png file that will be use by the save file.
  # Do whatever you want here, the following code is just a demo
  # I've extended the Bitmap class a bit to include some common Window
  # methods like draw_actor_level. Feel free to experiment new ways of creating
  # the picture !
  #--------------------------------------------------------------------------
  
  def rataime_generate_png_file_from_template
    # We load Graphics\Pictures\save_template_demo.png to use as a background
    bitmap = RPG::Cache.picture('save_template_demo')
    
    # We draw the 4 actor graphics and their levels
    bitmap.draw_actor_graphic($game_party.actors[0],52,277)
    bitmap.draw_actor_level($game_party.actors[0], 73, 237)
    if $game_party.actors[1] != nil
      bitmap.draw_actor_graphic($game_party.actors[1],52,350)
      bitmap.draw_actor_level($game_party.actors[1], 73, 310)
      if $game_party.actors[2] != nil
        bitmap.draw_actor_graphic($game_party.actors[2],167,277)
        bitmap.draw_actor_level($game_party.actors[2], 188, 237)
        if $game_party.actors[3] != nil
          bitmap.draw_actor_graphic($game_party.actors[3],167,350)
          bitmap.draw_actor_level($game_party.actors[3], 188, 310)
        end
      end
    end
    
    # We draw the play time
    bitmap.draw_playtime(100,182)
    
    # We draw the battleback of the last map on our picture
    # The size is ajusted so the recatngle is filled without stretching
    # the battleback, by discarding the sides of the battleback
    battleback = RPG::Cache.battleback($game_map.battleback_name)
    from = Rect.new(42, 0, 556, 320)
    to = Rect.new(32, 49, 217, 125)
    bitmap.stretch_blt(to, battleback, from)
    
    return bitmap
  end
  
  alias rataime_image_save_file_on_decision on_decision

  def on_decision(filename)
    if IMAGE_SAVEFILE_SAVE_AS_PNG
      filename = filename.split('.')[0] + '.png'
    end
    rataime_image_save_file_on_decision(filename)
  end
  
  alias rataime_image_save_file_write_save_data write_save_data
    
  def write_save_data(file)
    if IMAGE_SAVEFILE_SAVE_AS_PNG
      rataime_generate_png_file_from_template.rataime_write_png(file)
    end
    rataime_image_save_file_write_save_data(file)
  end
  
end

class Scene_Load < Scene_File

  alias rataime_image_save_file_on_decision on_decision
  alias rataime_image_save_file_read_save_data read_save_data

  def on_decision(filename)
    @extension = filename.split('.')[-1].downcase
    rataime_image_save_file_on_decision(filename)
  end
  
  def read_save_data(file)
    if @extension.downcase == 'png'
      file.gets('IEND')
      4.times do
        file.getc
      end
    end
    rataime_image_save_file_read_save_data(file)
  end
  
end

class Scene_File
  
  alias rataime_image_save_file_make_filename make_filename

  def make_filename(file_index)
    if FileTest.exist?("Save#{file_index + 1}.png")
      return "Save#{file_index + 1}.png"
    else
      return "Save#{file_index + 1}.rxdata"
    end
  end
  
end

class Scene_Title
  
  alias rataime_image_save_file_main_test_continue main_test_continue

  def main_test_continue
    rataime_image_save_file_main_test_continue
    for i in 0..3
      if FileTest.exist?("Save#{i+1}.png")
        # Sets Continued Enable Flag On
        @continue_enabled = true
      end
    end
  end

end

class Window_SaveFile < Window_Base
  
  alias rataime_image_save_file_init_gamedata init_gamedata
  
  def init_gamedata
    if @filename.split('.')[-1].downcase == 'png'
      file = File.open(@filename, "rb")
      @time_stamp = file.mtime
      file.gets('IEND')
      4.times do
        file.getc
      end
      @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
    else
      rataime_image_save_file_init_gamedata
    end
  end
end
#============================================================================== 
# ** Bitmap     
#==============================================================================

class Bitmap

  def normal_color
    return Color.new(255, 255, 255, 255)
  end
  
  def system_color
    return Color.new(192, 224, 255, 255)
  end
  
  def draw_actor_graphic(actor, x, y)
    bmp = RPG::Cache.character(actor.character_name, actor.character_hue)
    cw = bmp.width / 4
    ch = bmp.height / 4
    src_rect = Rect.new(0, 0, cw, ch)
    self.blt(x - cw / 2, y - ch, bmp, src_rect)
  end
  
  def draw_playtime(x,y)
    total_sec = Graphics.frame_count / Graphics.frame_rate
    hour = total_sec / 60 / 60
    min = total_sec / 60 % 60
    sec = total_sec % 60
    text = sprintf("%02d:%02d:%02d", hour, min, sec)
    self.font.color = normal_color
    self.draw_text(x, y, 120, 32, text, 2)
  end
  
  def draw_actor_level(actor, x, y)
    self.font.color = system_color
    self.draw_text(x, y, 32, 32, "Lv")
    self.font.color = normal_color
    self.draw_text(x + 24, y, 24, 32, actor.level.to_s, 2)
  end
  
  def rataime_write_png(f)
    Zlib::Png_File.open('temp.gz')   { |gz| gz.make_png(self, 0) }
    Zlib::GzipReader.open('temp.gz') { |gz| $read = gz.read }
    f.write($read)
    File.delete('temp.gz')
  end
end
#============================================================================== 
# ** Modules.Zlib
#------------------------------------------------------------------------------
# Description:
# ------------
# Adds PNG_File class to save Bitmap's to PNG Files
#  
# Class List:
# -----------
# Png_File
#==============================================================================

#============================================================================== 
# ** Zlib     
#==============================================================================

module Zlib
  #============================================================================ 
  # ** Png_File     
  #-------------------------------------------------------------------------
  #   Info      : Saves Bitmap to File
  #   Author    : ??? - http://www.66rpg.com/htm/news624.htm
  #============================================================================

  class Png_File < GzipWriter
    #--------------------------------------------------------------------------
    # * Make PNG
    #--------------------------------------------------------------------------
    def make_png(bitmap, mode = 0)
      # Save Bitmap & Mode
      @bitmap, @mode = bitmap, mode
      # Create & Save PNG
      self.write(make_header)
      self.write(make_ihdr)
      self.write(make_idat)
      self.write(make_iend)
    end
    #--------------------------------------------------------------------------
    # * Make Header
    #--------------------------------------------------------------------------
    def make_header
      return [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a].pack('C*')
    end
    #--------------------------------------------------------------------------
    # * Make IHDR
    #--------------------------------------------------------------------------
    def make_ihdr
      ih_size               = [13].pack("N")
      ih_sign               = 'IHDR'
      ih_width              = [@bitmap.width].pack('N')
      ih_height             = [@bitmap.height].pack('N')
      ih_bit_depth          = [8].pack('C')
      ih_color_type         = [6].pack('C')
      ih_compression_method = [0].pack('C')
      ih_filter_method      = [0].pack('C')
      ih_interlace_method   = [0].pack('C')
      string = ih_sign + ih_width + ih_height + ih_bit_depth + ih_color_type +
               ih_compression_method + ih_filter_method + ih_interlace_method
      ih_crc = [Zlib.crc32(string)].pack('N')
      return ih_size + string + ih_crc
    end
    #--------------------------------------------------------------------------
    # * Make IDAT
    #--------------------------------------------------------------------------
    def make_idat
      header  = "\x49\x44\x41\x54"
      data    = @mode == 0 ? make_bitmap_data0 : make_bitmap_data1
      data    = Zlib::Deflate.deflate(data, 8)
      crc     = [Zlib.crc32(header + data)].pack('N')
      size    = [data.length].pack('N')
      return size + header + data + crc
    end
    #--------------------------------------------------------------------------
    # * Make Bitmap Data 0
    #--------------------------------------------------------------------------
    def make_bitmap_data0
      gz = Zlib::GzipWriter.open('hoge.gz')
      t_Fx = 0
      w = @bitmap.width
      h = @bitmap.height
      data = []
      for y in 0...h
        data.push(0)
        for x in 0...w
          t_Fx += 1
          if t_Fx % 10000 == 0
            Graphics.update
          end
          if t_Fx % 100000 == 0
            s = data.pack("C*")
            gz.write(s)
            data.clear
          end
          color = @bitmap.get_pixel(x, y)
          red = color.red
          green = color.green
          blue = color.blue
          alpha = color.alpha
          data.push(red)
          data.push(green)
          data.push(blue)
          data.push(alpha)
        end
      end
      s = data.pack("C*")
      gz.write(s)
      gz.close   
      data.clear
      gz = Zlib::GzipReader.open('hoge.gz')
      data = gz.read
      gz.close
      File.delete('hoge.gz')
      return data
    end
    #--------------------------------------------------------------------------
    # * Make Bitmap Data Mode 1
    #--------------------------------------------------------------------------
    def make_bitmap_data1
      w = @bitmap.width
      h = @bitmap.height
      data = []
      for y in 0...h
        data.push(0)
        for x in 0...w
          color = @bitmap.get_pixel(x, y)
          red = color.red
          green = color.green
          blue = color.blue
          alpha = color.alpha
          data.push(red)
          data.push(green)
          data.push(blue)
          data.push(alpha)
        end
      end
      return data.pack("C*")
    end
    #--------------------------------------------------------------------------
    # * Make IEND
    #--------------------------------------------------------------------------
    def make_iend
      ie_size = [0].pack('N')
      ie_sign = 'IEND'
      ie_crc  = [Zlib.crc32(ie_sign)].pack('N')
      return ie_size + ie_sign + ie_crc
    end
  end
end

And here's the demo :

http://rataime.free.fr/rmxp/Demos/rataime_demo.zip

Enjoy !
 
WHOA!!!! Long time no SEE, rataime!!!!!

Talk about taking a long vacation!!!!!!!!!

Glad to see you're still kickin' as well as ever!!!
 
Samot":19evep0k said:
Just one question though, will the saved data change when the images change?

No, absolutely not, it is distinct form the picture. However, don't open it in an editor and save it afterward, the data would disappear.
 
The concept of this is pretty cool, instead of opening your save folder and seeing a bunch of identical .rxdata files, you can actually see the images of your saves in your physical Saves folder! (or wherever you keep your save files)

I do have a question though, if you don't mind. Can you explain how you figured out to read/write the data from an image? I thought that was only possible to do in certain things like text documents and rx/vx data files, never thought to do it with a graphics file, that is really neat!

Matter of fact, all of your scripts in your demo are somehow graphics-related, such as the shadows script, and your mirror script, destruction script, which are all pretty effin' awesome! Makes me want to learn something new! Great job rataime! :thumb:
 
Kain Nobel":sa085zpz said:
I do have a question though, if you don't mind. Can you explain how you figured out to read/write the data from an image?

I cheated  :wink:
You can obviously read/write any binary file in ruby. PNG files (along with jpeg files) have a nice property, which is to have a tag which tells the reader to stop reading the png file. It's actually "IEND" followed by a 4-byte CRC.

When reading the save, I just tell ruby to read the first part of the file, and then I can use functions like Marshall.load to read the stream as if the file was a normal save.
 

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