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.

RGSS Table, Color & Tone Classes

I have made a Table class, equal to the RGSS one, which includes Marshal dump and load methods. You should be asking, why to use this, if it is equal to the RMXP/VX one, but this was designed for pure ruby, where the RGSS classes doens't exist. So with this script you will be able to load some .rxdata/.rvdata, which uses the Table class, with .rb programs, and also edit them and save them again.
Code:
class Table

  def initialize(x,y=1,z=1)

    @xsize,@ysize,@zsize=x,y,z

    @data=Array.new(x*y*z, 0)

  end

  def [](x,y=0,z=0)

    @data[x+y*@xsize+z*@xsize*@ysize]

  end

  def []=(*args)

    x=args[0]

    y=args.size>2 ?args[1]:0

    z=args.size>3 ?args[2]:0

    v=args.pop

    @data[x+y*@xsize+z*@xsize*@ysize]=v

  end

  def _dump(d=0)

    s=[3].pack('L')

    s+=[@xsize].pack('L')+[@ysize].pack('L')+[@zsize].pack('L')

    s+=[@xsize*@ysize*@zsize].pack('L')

    for z in 0...@zsize

      for y in 0...@ysize

        for x in 0...@xsize

          s+=[@data[x+y*@xsize+z*@xsize*@ysize],0,0].pack('L')[0,2]

        end

      end

    end

    s

  end

  attr_reader(:xsize,:ysize,:zsize,:data)

  class << self

    def _load(s)

      size=s[0,4].unpack('L')[0]

      nx=s[4,4].unpack('L')[0]

      ny=s[8,4].unpack('L')[0]

      nz=s[12,4].unpack('L')[0]

      data=[]

      pointer=20

      loop do

        data.push((s[pointer,2]+"\000\000").unpack('L')[0])

        pointer+=2

        break if pointer > s.size-1

      end

      t=Table.new(nx,ny,nz)

      n=0

      for z in 0...nz

        for y in 0...ny

          for x in 0...nx

            t[x,y,z]=data[n]

            n+=1

          end

        end

      end

      t

    end

  end

end

And also I include here my BigTable class, which was designed for RPG Advanced Editor. This Table class allows to use upto 4294967296 numbers, insteand of the 65535 of the normal Table class.
Code:
class BigTable

  def initialize(x, y = 1, z = 1)

    x = [x, 256 ** 4].min

    y = [y, 256 ** 4].min

    z = [z, 256 ** 4].min

    @xsize, @ysize, @zsize = x, y, z

    @data = Array.new(x * y * z, 0)

  end

  def [](x, y = 0, z = 0)

    x = [x, @xsize].min

    y = [y, @ysize].min

    z = [z, @zsize].min

    @data[x + y * @xsize + z * @xsize * @ysize]

  end

  def []=(*args)

    x = [args[0], @xsize].min

    y = [args.size>2 ?args[1] : 0, @ysize].min

    z = [args.size>3 ?args[2] : 0, @zsize].min

    v = [args.pop, 256 ** 4].min

    @data[x + y * @xsize + z * @xsize * @ysize] = v

  end

  def _dump(d = 0)

    s = [3].pack('L')

    s += [@xsize].pack('L') + [@ysize].pack('L') + [@zsize].pack('L')

    s += [@xsize * @ysize * @zsize].pack('L')

    for z in 0...@zsize

      for y in 0...@ysize

        for x in 0...@xsize

          s += [@data[x + y * @xsize + z * @xsize * @ysize]].pack('L')

        end

      end

    end

    s

  end

  attr_reader(:xsize, :ysize, :zsize, :data)

  class << self

    def _load(s)

      nx = s[0, 4].unpack('L')[0]

      ny = s[4, 4].unpack('L')[0]

      nz = s[8, 4].unpack('L')[0]

      size = s[12, 4].unpack('L')[0]

      data = []

      pointer = 16

      loop do

        data.push(s[pointer, 4].unpack('L')[0])

        pointer += 4

        break if pointer > s.size - 1

      end

      t = BigTable.new(nx, ny, nz)

      n = 0

      for z in 0...nz

        for y in 0...ny

          for x in 0...nx

            t[x, y, z] = data[n]

            n += 1

          end

        end

      end

      t

    end

  end

end

Update - 25/03/09
I have made time ago Color and Tone classes with their marshal methods, again for my project RPG Advanced Editor. Here are both classes, and also the last version of the Table class.

Color:
Code:
class Color

  def initialize(r, g, b, a = 255)

    @red = r

    @green = g

    @blue = b

    @alpha = a

  end

  def set(r, g, b, a = 255)

    @red = r

    @green = g

    @blue = b

    @alpha = a

  end

  def color

    Color.new(@red, @green, @blue, @alpha)

  end

  def _dump(d = 0)

    [@red, @green, @blue, @alpha].pack('d4')

  end

  def self._load(s)

    Color.new(*s.unpack('d4'))

  end

  attr_accessor(:red, :green, :blue, :alpha)

end

Tone:
Code:
class Tone

  def initialize(r, g, b, a = 0)

    @red = r

    @green = g

    @blue = b

    @gray = a

  end

  def set(r, g, b, a = 0)

    @red = r

    @green = g

    @blue = b

    @gray = a

  end

  def color

    Color.new(@red, @green, @blue, @gray)

  end

  def _dump(d = 0)

    [@red, @green, @blue, @gray].pack('d4')

  end

  def self._load(s)

    Tone.new(*s.unpack('d4'))

  end

  attr_accessor(:red, :green, :blue, :gray)

end

And my last version of Table:
Code:
class Table

  def initialize(x, y = 1, z = 1)

    @xsize, @ysize, @zsize = x, y, z

    @data = Array.new(x * y * z, 0)

  end

  def [](x, y = 0, z = 0)

    @data[x + y * @xsize + z * @xsize * @ysize]

  end

  def []=(*args)

    x = args[0]

    y = args.size > 2 ? args[1] :0

    z = args.size > 3 ? args[2] :0

    v = args.pop

    @data[x + y * @xsize + z * @xsize * @ysize] = v

  end

  def _dump(d = 0)

    s = [3].pack('L')

    s += [@xsize].pack('L') + [@ysize].pack('L') + [@zsize].pack('L')

    s += [@xsize * @ysize * @zsize].pack('L')

    for z in 0...@zsize

      for y in 0...@ysize

        for x in 0...@xsize

          s += [@data[x + y * @xsize + z * @xsize * @ysize]].pack('S')

        end

      end

    end

    s

  end

  def self._load(s)

    size = s[0, 4].unpack('L')[0]

    nx = s[4, 4].unpack('L')[0]

    ny = s[8, 4].unpack('L')[0]

    nz = s[12, 4].unpack('L')[0]

    data = []

    pointer = 20

    loop do

      data.push(*s[pointer, 2].unpack('S'))

      pointer += 2

      break if pointer > s.size - 1

    end

    t = Table.new(nx, ny, nz)

    n = 0

    for z in 0...nz

      for y in 0...ny

        for x in 0...nx

          t[x, y, z] = data[n]

          n += 1

        end

      end

    end

    t

  end

  attr_reader(:xsize, :ysize, :zsize, :data)

end
 

OS

Sponsor

It's for people who want to make things like Tilemaps in straight-up Ruby, right?

From what I can see, you'd be able to make a new map editor to work with an enhanced version of the Tilemap so that you could have more layers, autotiles, and whatever new features you could imagine, and be able to put those maps in your RMXP/VX game.

Pretty sweet. Good job, vgvgf.
 
Nice one, I especially like the BigTable class.
The only thing I don't quite understand is the .pack/.unpack methods... These are still mysterious to me.

Great job! :thumb:
-Dargor
 
King Red Of Pancakes":3qpsnski said:
So... what exactly is this for? Some kind osf scripting tool?
  -KRoP
OS":3qpsnski said:
It's for people who want to make things like Tilemaps in straight-up Ruby, right?

From what I can see, you'd be able to make a new map editor to work with an enhanced version of the Tilemap so that you could have more layers, autotiles, and whatever new features you could imagine, and be able to put those maps in your RMXP/VX game.

Pretty sweet. Good job, vgvgf.
Well, I designed the Table class for allowing the load of Marshal data via .rb files. You can put the RPG module, with the definition of the RPG::#### classes in a .rb file, but some classes, like RPG::Actors, which uses the Table class for the paramaters, can't be loaded except you have the Table class defined, like my script.
And the BigTable class was designed for breaking the RMXP limits, like 9999 max stats, and so, which also can be used for making better tilesets, as you said OS.
Dargor":3qpsnski said:
Nice one, I especially like the BigTable class.
The only thing I don't quite understand is the .pack/.unpack methods... These are still mysterious to me.

Great job! :thumb:
-Dargor
The pack/unpack methods are for compressing and decompressing data into and from bytes/numbers or strings. This methods are useful for some API calls, which gives or requires the data compressed, and in this case is useful, because compressed/packed numbers occupies less space when dumped than the normal numbers.
 

Raku

Member

@vgvgf => You rock! This is going to be very helpful!! I'm writing an importer/exporter to plain text for all the rxdata files so that teams of people can work on RMXP games together and still version them with something like SVN or Mercurial.

By the way, how did you figure out the _load and _dump functions?!! I am working on the Color and Tone classes, but I am not sure how RGSS implemented the dump/load logic. Is there an easy/clever way to figure it out?

Thanks!
 
Interesting... and definately helpful for understanding that class. Also comes right when I'm working on Table-relying scripts :]

What are the chances you'll attempt a Tilemap class rewrite? ^^ (for VX, that is, since Seph already has the XP one covered...)
 
Raku":10819pg4 said:
@vgvgf => You rock! This is going to be very helpful!! I'm writing an importer/exporter to plain text for all the rxdata files so that teams of people can work on RMXP games together and still version them with something like SVN or Mercurial.

By the way, how did you figure out the _load and _dump functions?!! I am working on the Color and Tone classes, but I am not sure how RGSS implemented the dump/load logic. Is there an easy/clever way to figure it out?

Thanks!
I added Color and Tone classes to the first post. About how to figure how these classes are marshaled, well, I just analized the resultant strings lots of times until I encountered some patterns. That's all.

What are the chances you'll attempt a Tilemap class rewrite? ^^ (for VX, that is, since Seph already has the XP one covered...)
100%, hehe, I have already made a Tilemap rewrite which wasnt very good, but now I am thinking on making it via dll with poccil's method, which would result in a really fast Tilemap script. But I am still learning C, so it will take me some time.
 

Raku

Member

Thanks vgvgf! Using your classes I can now read all the rxdata files and dump their contents to YAML files! Now all I need to do is tidy up the scripting, test it out, then its ready to share. Thanks for sharing your work, I guess I need to improve my binary hacking skills. o(>_<)o

The version you provided here works great, but if we want to be 100% compliant with the RGSS documentation, here are revised Color and Tone classes with two minor changes:
  • The attributes are stored as Floats, not FixNums
  • Attribute setters limit the assigned values if they are outside the boundaries specified in the documentation.

Code:
class Color

  def initialize(r, g, b, a = 255.0)

    self.red = r.to_f

    self.green = g.to_f

    self.blue = b.to_f

    self.alpha = a.to_f

  end

  def set(r, g, b, a = 255.0)

    self.red = r.to_f

    self.green = g.to_f

    self.blue = b.to_f

    self.alpha = a.to_f

  end

  attr_reader(:red, :green, :blue, :alpha)

  def red=(val)

    @red   = [[val.to_f, 0.0].max, 255.0].min

  end

  def green=(val)

    @green = [[val.to_f, 0.0].max, 255.0].min

  end

  def blue=(val)

    @blue  = [[val.to_f, 0.0].max, 255.0].min

  end

  def alpha=(val)

    @alpha = [[val.to_f, 0.0].max, 255.0].min

  end

  def color

    Color.new(@red, @green, @blue, @alpha)

  end

  def _dump(d = 0)

    [@red, @green, @blue, @alpha].pack('d4')

  end

  def self._load(s)

    Color.new(*s.unpack('d4'))

  end

end

 

class Tone

  def initialize(r, g, b, a = 0.0)

    self.red = r.to_f

    self.green = g.to_f

    self.blue = b.to_f

    self.gray = a.to_f

  end

  def set(r, g, b, a = 0.0)

    self.red = r.to_f

    self.green = g.to_f

    self.blue = b.to_f

    self.gray = a.to_f

  end

  def color

    Color.new(@red, @green, @blue, @gray)

  end

  def _dump(d = 0)

    [@red, @green, @blue, @gray].pack('d4')

  end

  def self._load(s)

    Tone.new(*s.unpack('d4'))

  end

  attr_reader(:red, :green, :blue, :gray)

  def red=(val)

    @red   = [[val.to_f, -255.0].max, 255.0].min

  end

  def green=(val)

    @green = [[val.to_f, -255.0].max, 255.0].min

  end

  def blue=(val)

    @blue  = [[val.to_f, -255.0].max, 255.0].min

  end

  def gray=(val)

    @gray  = [[val.to_f, 0.0].max, 255.0].min

  end

end
 

Raku

Member

I have updated your Table class so that RMXP can successfully read .rxdata files dumped by it. The two versions you wrote were loading the Table objects correctly from RMXP's .rxdata files, but your _dump logic always packed the constant 3 first. RMXP was choking when it tried to open an .rxdata file dumped using that logic because that first packed value represents the number of dimensions specified when creating the Table (1, 2, or 3). Not sure why the built-in Table class cares, maybe some optimization of the under-lying C/C++ implementation.

I've updated the Table class below with the correct dump logic so that dumped rxdata files can be correctly read by RMXP.

Code:
class Table

  def initialize(x, y = 0, z = 0)

    @dim = 1 + (y > 0 ? 1 : 0) + (z > 0 ? 1 : 0)

    @xsize, @ysize, @zsize = x, (y > 0 ? y : 1), (z > 0 ? z : 1)

    @data = Array.new(x * y * z, 0)

  end

  def [](x = 0, y = 0, z = 0)

    @data[x + y * @xsize + z * @xsize * @ysize]

  end

  def []=(*args)

    x = args[0]

    y = args.size > 2 ? args[1] :0

    z = args.size > 3 ? args[2] :0

    v = args.pop

    @data[x + y * @xsize + z * @xsize * @ysize] = v

  end

  def _dump(d = 0)

    # The number of required dimensions

    s = [@dim].pack('L')

    s += [@xsize].pack('L') + [@ysize].pack('L') + [@zsize].pack('L')

    s += [@xsize * @ysize * @zsize].pack('L')

    @zsize.times do |z|

      @ysize.times do |y|

        @xsize.times do |x|

          s += [@data[x + y * @xsize + z * @xsize * @ysize]].pack('S')

        end

      end

    end

    s

  end

  def self._load(s)

    size = s[0, 4].unpack('L')[0]

    nx = s[4, 4].unpack('L')[0]

    ny = s[8, 4].unpack('L')[0]

    nz = s[12, 4].unpack('L')[0]

    data = []

    pointer = 20

    loop do

      data.push(*s[pointer, 2].unpack('S'))

      pointer += 2

      break if pointer > s.size - 1

    end

    t = Table.new(nx, ny, nz) if size == 3

    t = Table.new(nx, ny)     if size == 2

    t = Table.new(nx)         if size == 1

    n = 0

    for z in 0...nz

      for y in 0...ny

        for x in 0...nx

          t[x, y, z] = data[n]

          n += 1

        end

      end

    end

    t

  end

  attr_reader(:xsize, :ysize, :zsize)

end

Finally, I got my practice with reading the binary dumps! :thumb:
 
Thanks Raku, the first long of the table dump was a mistery to me, I didn't know that it was the number of dimesions. It was a unnecessary value for my table class, as it didn't depend on the dimension number, so I just put "3", as I saw in some RMXP Table dumps.
I have also updated the Table class, now it is cleaner:
Code:
class Table

  def initialize(x, y = 0, z = 0)

    @dim = 1 + (y > 0 ? 1 : 0) + (z > 0 ? 1 : 0)

    @xsize, @ysize, @zsize = x, [y, 1].max, [z, 1].max

    @data = Array.new(x * y * z, 0)

  end

  def [](x, y = 0, z = 0)

    @data[x + y * @xsize + z * @xsize * @ysize]

  end

  def []=(*args)

    x = args[0]

    y = args.size > 2 ? args[1] : 0

    z = args.size > 3 ? args[2] : 0

    v = args.pop

    @data[x + y * @xsize + z * @xsize * @ysize] = v

  end

  def _dump(d = 0)

    [@dim, @xsize, @ysize, @zsize, @xsize * @ysize * @zsize].pack('LLLLL') <<

    @data.pack("S#{@xsize * @ysize * @zsize}")

  end

  def self._load(s)

    size, nx, ny, nz, items = *s[0, 20].unpack('LLLLL')

    case size

    when 1; t = Table.new(nx)

    when 2; t = Table.new(nx, ny)

    when 3; t = Table.new(nx, ny, nz)

    end

    t.data = s[20, items * 2].unpack("S#{items}")

    t

  end

  attr_accessor(:xsize, :ysize, :zsize, :data)

end

And also, you made a great work with the Color and Tone classes, now they are 100% RGSS compatible.

PD: Thanks to trebor777, who notified me that for strings the << method is faster than the + method.
 
Code:
case size

when 1; t = Table.new(nx)

when 2; t = Table.new(nx, ny)

when 3; t = Table.new(nx, ny, nz)

end

better this:
Code:
t= Table.new(*[nx,ny,nz][0,size])

its very shorter :P
 

Raku

Member

Nice clean-up of the Table class, vgvgf! I'll include it in my next release of the Rxdata Versioning Utility.

vgvgf":21xf20g5 said:
And also, you made a great work with the Color and Tone classes, now they are 100% RGSS compatible.
No problem! I was just "standing on the shoulders of giants" so to speak. ;)

hanmac":21xf20g5 said:
better this:
Code:
t= Table.new(*[nx,ny,nz][0,size])

its very shorter :P
hanmac, that is indeed compact! But I have no idea what the * in that Ruby code snippet does! :) Can someone enlighten me?
 

Zeriab

Sponsor

The * breaks up an array into arguments.
[rgss]my_method(*[nx,ny,nz])
# Is basically the same as
my_method(nx,ny,nz)
[/rgss]
 

Raku

Member

Ahhh...thanks Zeriab! Got it.

I tend to agree with Zeriab that the case statement is probably faster and has the benefit of being easier to understand.

But, thanks to hanmac for his post regardless! I learned something new today. :)
 
Actually if we know how works the table we should be able to write directly

t = Table.new(nx, ny, nz)

for all the cases... as ny and nz, should be set to 0 by the unpack if the loaded tabled only used one or two dimensions.

Secondly you have a small bug that may happen if you try to dump a table which has just been created with only 1 or 2 dim
I think it's related to this; @data.pack('S#{size}') crashes "too few arguments".

I'll later tonight try to fix and improve that anyway.


EDIT^^
Al right Here is the fixed version:

Not Having a 2nd dimension is the same as having a 2nd dimension which only contains 1 item. (can be applied for a 3rd Dimension)
Therefore instead of increasing the dim size if the y or z are greater than 0, we do it if those are greater than 1
The default value for y and z also becomes 1.

Code:
 

class Table

  def initialize(x, y = 1, z = 1)

    y = [y.abs,1].max # Just in case the user types 0, or a negative value

    z = [z.abs,1].max

    @dim = 1 + (y <=> 1) + (z <=> 1)   # <=> returns 1 if greater, 0 if the same, -1 if smaller

    @xsize, @ysize, @zsize = x, y, z # We don't need to set the 1 to 1 ;)

    @data = Array.new(@xsize * @ysize * @zsize, 0)

  end

  def [](x, y = 0, z = 0)

    @data[x + y * @xsize + z * @xsize * @ysize]

  end

  def []=(*args)

    x = args[0]

    y = args.size > 2 ? args[1] : 0

    z = args.size > 3 ? args[2] : 0

    v = args.pop

    @data[x + y * @xsize + z * @xsize * @ysize] = v

  end

  def _dump(d = 0)

    [@dim, @xsize, @ysize, @zsize, @xsize * @ysize * @zsize].pack('LLLLL') << @data.pack("S#{@xsize * @ysize * @zsize}")

  end

  def self._load(s)

    size, nx, ny, nz, items = *s[0, 20].unpack('LLLLL')

    t = Table.new(nx, ny, nz)

    t.data = s[20, items * 2].unpack("S#{items}")

    t

  end

  attr_accessor(:xsize, :ysize, :zsize, :data)

end

 

PS... This hack transforms thhe into the ...great
 

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