RMX-OS port to VXAce

Started by Mason Wheeler, March 28, 2016, 06:57:07 am

Previous topic - Next topic

Mason Wheeler

March 28, 2016, 06:57:07 am Last Edit: April 03, 2016, 12:20:36 pm by Mason Wheeler
So I recently ran across RMX-OS, and it looks really awesome, except for one thing: it doesn't work in RGSS3.  I'm working on something that would really benefit from some of the features in RMX-OS, so I decided to try my hand at porting it.  This is still a work in progress, but here's what I've got so far.

First, I extracted the Input module into its own script, because it really feels like its own distinct script anyway.  It had to be updated to deal with RGSS3's symbol-based input.  I feel like the way I did it was a bit of a hack, and it could probably be improved upon, but for the moment, it works without crashing.

Spoiler: ShowHide
Code: ruby

#:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=
# Custom Game Controls by Blizzard
# Version: 4.1 RMX-OS Edition
#:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=
#
# Compatiblity:
#
#   99% compatible with SDK 1.x, 90% compatible with SDK 2.x.
#
#
# Note:
#
#   Why is this input module better than others? I has far less code and it
#   can handle keyboard language layout.
#
#
# Explanation & Configuration:
#
#   This Add-on will allow you to specify your own game controls. Just below
#   is a list of possible keys, below that is the configuration. The default
#   configuration is RMXP's real game control configuration. You can add any
#   key specification into a key array and separate them with commas. Example:
#   
#   RIGHT = [Key['Arrow Right'], Key[','], Key['F'], Key['Ctrl'], Key['3'],
#            Key['NumberPad 6'], Key['F3'], Key['\''], Key['\\']]
#   
#   This example would assign for the RIGHT button the following keys:
#   - directional right (right arrow key)
#   - comma
#   - letter key F
#   - Control key (CTRL)
#   - Number Key 3 (on top over the letter keys)
#   - Numberpad Key 6 (number 6 on the numberpad on the right)
#   - Functional Key 3 (F3)
#   - apostrophe (')
#   - backslash (\)
#:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=

if !$tons_version || !TONS_OF_ADDONS::CUSTOM_CONTROLS
 
#==============================================================================
# module Input
#==============================================================================

module Input
 
  #----------------------------------------------------------------------------
  # Simple ASCII table
  #----------------------------------------------------------------------------
  Key = {'A' => 65, 'B' => 66, 'C' => 67, 'D' => 68, 'E' => 69, 'F' => 70,
         'G' => 71, 'H' => 72, 'I' => 73, 'J' => 74, 'K' => 75, 'L' => 76,
         'M' => 77, 'N' => 78, 'O' => 79, 'P' => 80, 'Q' => 81, 'R' => 82,
         'S' => 83, 'T' => 84, 'U' => 85, 'V' => 86, 'W' => 87, 'X' => 88,
         'Y' => 89, 'Z' => 90,
         '0' => 48, '1' => 49, '2' => 50, '3' => 51, '4' => 52, '5' => 53,
         '6' => 54, '7' => 55, '8' => 56, '9' => 57,
         'NumberPad 0' => 45, 'NumberPad 1' => 35, 'NumberPad 2' => 40,
         'NumberPad 3' => 34, 'NumberPad 4' => 37, 'NumberPad 5' => 12,
         'NumberPad 6' => 39, 'NumberPad 7' => 36, 'NumberPad 8' => 38,
         'NumberPad 9' => 33,
         'F1' => 112, 'F2' => 113, 'F3' => 114, 'F4' => 115, 'F5' => 116,
         'F6' => 117, 'F7' => 118, 'F8' => 119, 'F9' => 120, 'F10' => 121,
         'F11' => 122, 'F12' => 123,
         ';' => 186, '=' => 187, ',' => 188, '-' => 189, '.' => 190, '/' => 220,
         '\\' => 191, '\'' => 222, '[' => 219, ']' => 221, '`' => 192,
         'Backspace' => 8, 'Tab' => 9, 'Enter' => 13, 'Shift' => 16,
         'Left Shift' => 160, 'Right Shift' => 161, 'Left Ctrl' => 162,
         'Right Ctrl' => 163, 'Left Alt' => 164, 'Right Alt' => 165,
         'Ctrl' => 17, 'Alt' => 18, 'Esc' => 27, 'Space' => 32, 'Page Up' => 33,
         'Page Down' => 34, 'End' => 35, 'Home' => 36, 'Insert' => 45,
         'Delete' => 46, 'Arrow Left' => 37, 'Arrow Up' => 38,
         'Arrow Right' => 39, 'Arrow Down' => 40,
         'Mouse Left' => 1, 'Mouse Right' => 2, 'Mouse Middle' => 4,
         'Mouse 4' => 5, 'Mouse 5' => 6}
#::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
# START Configuration
#::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
  UP = [Key['Arrow Up']]
  LEFT = [Key['Arrow Left']]
  DOWN = [Key['Arrow Down']]
  RIGHT = [Key['Arrow Right']]
  A = [Key['Shift']]
  B = [Key['Esc']]
  C = [Key['Enter']]
  X = [Key['A']]
  Y = [Key['S']]
  Z = [Key['D']]
  L = [Key['Page Down']]
  R = [Key['Page Up']]
  F5 = [Key['F5']]
  F6 = [Key['F6']]
  F7 = [Key['F7']]
  F8 = [Key['F8']]
  F9 = [Key['F9']]
  SHIFT = [Key['Shift']]
  CTRL = [Key['Ctrl']]
  ALT = [Key['Alt']]
  pagedown = [Key['Page Down']]
  pageup = [Key['Page Up']]
#::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
# END Configuration
#::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
  # All keys
  KEY_COUNT = 256
  ALL_KEYS = (0...KEY_COUNT).to_a
  # Win32 API calls
  GetKeyboardState = Win32API.new('user32', 'GetKeyboardState', 'P', 'I')
  GetKeyboardLayout = Win32API.new('user32', 'GetKeyboardLayout', 'L', 'L')
  MapVirtualKeyEx = Win32API.new('user32', 'MapVirtualKeyEx', 'IIL', 'I')
  ToUnicodeEx = Win32API.new('user32', 'ToUnicodeEx', 'LLPPILL', 'L')
  # some other constants
  DOWN_STATE_MASK = 0x80
  DEAD_KEY_MASK = 0x80000000
  # data
  @state = "\0" * KEY_COUNT
  @triggered = {}
  @pressed = {}
  @released = {}
  @repeatedKey = -1
  @repeatedCount = 0
  #----------------------------------------------------------------------------
  # update
  #  Updates input.
  #----------------------------------------------------------------------------
  def self.update
    # get current language layout
    @language_layout = GetKeyboardLayout.call(0)
    # get new keyboard state
    GetKeyboardState.call(@state)
    # this special code is used because Ruby 1.9.x does not return a char
    # when using String#[] but another String
    key = 0
    @state.each_byte {|byte|
      # if pressed state
      if (byte & DOWN_STATE_MASK) == DOWN_STATE_MASK
        # not released anymore
        @released[key] = false
        # if not pressed yet
        if !@pressed[key]
          # pressed and triggered
          @pressed[key] = true
          @triggered[key] = true
          @repeatedKey = key
          @repeatedCount = 0
        else
          # not triggered anymore
          @triggered[key] = false
        end
        # update of repeat counter
        if key == @repeatedKey
          @repeatedCount < 17 ? @repeatedCount += 1 : @repeatedCount = 15
        end
      # not released yet
      elsif !@released[key]
        # if still pressed
        if @pressed[key]
          # not triggered, pressed or repeated, but released
          @triggered[key] = false
          @pressed[key] = false
          @released[key] = true
          if key == @repeatedKey
            @repeatedKey = -1
            @repeatedCount = 0
          end
        end
      else
        # not released anymore
        @released[key] = false
      end
      key += 1
    }
  end
  #----------------------------------------------------------------------------
  # dir4
  #  4 direction check.
  #----------------------------------------------------------------------------
  def self.dir4
    return 2 if self.press?(DOWN)
    return 4 if self.press?(LEFT)
    return 6 if self.press?(RIGHT)
    return 8 if self.press?(UP)
    return 0
  end
  #----------------------------------------------------------------------------
  # dir8
  #  8 direction check.
  #----------------------------------------------------------------------------
  def self.dir8
    down = self.press?(DOWN)
    left = self.press?(LEFT)
    return 1 if down && left
    right = self.press?(RIGHT)
    return 3 if down && right
    up = self.press?(UP)
    return 7 if up && left
    return 9 if up && right
    return 2 if down
    return 4 if left
    return 6 if right
    return 8 if up
    return 0
  end
 
  def self.normalize_keys(keys)
    if keys.is_a?(Symbol)
      return Input.const_get(keys)
    elsif !keys.is_a?(Array)
      return [keys]
    end
    return keys
  end
 
  #----------------------------------------------------------------------------
  # trigger?
  #  Test if key was triggered once.
  #----------------------------------------------------------------------------
  def self.trigger?(keys)
    keys = normalize_keys(keys)
    return keys.any? {|key| @triggered[key]}
  end
  #----------------------------------------------------------------------------
  # press?
  #  Test if key is being pressed.
  #----------------------------------------------------------------------------
  def self.press?(keys)
    keys = normalize_keys(keys)
    return keys.any? {|key| @pressed[key]}
  end
  #----------------------------------------------------------------------------
  # repeat?
  #  Test if key is being pressed for repeating.
  #----------------------------------------------------------------------------
  def self.repeat?(keys)
    keys = normalize_keys(keys)
    return (@repeatedKey >= 0 && keys.include?(@repeatedKey) &&
        (@repeatedCount == 1 || @repeatedCount == 16))
  end
  #----------------------------------------------------------------------------
  # release?
  #  Test if key was released.
  #----------------------------------------------------------------------------
  def self.release?(keys)
    keys = normalize_keys(keys)
    return keys.any? {|key| @released[key]}
  end
  #----------------------------------------------------------------------------
  # get_character
  #  vk - virtual key
  #  Gets the character from keyboard input using the input locale identifier
  #  (formerly called keyboard layout handles).
  #----------------------------------------------------------------------------
  def self.get_character(vk)
    # get corresponding character from virtual key
    c = MapVirtualKeyEx.call(vk, 2, @language_layout)
    # stop if character is non-printable and not a dead key
    return '' if c < 32 && (c & DEAD_KEY_MASK) != DEAD_KEY_MASK
    # get scan code
    vsc = MapVirtualKeyEx.call(vk, 0, @language_layout)
    # result string is never longer than 4 bytes (Unicode)
    result = "\0" * 4
    # get input string from Win32 API
    length = ToUnicodeEx.call(vk, vsc, @state, result, 4, 0, @language_layout)
    return (length == 0 ? '' : result)
  end
  #----------------------------------------------------------------------------
  # get_input_string
  #  Gets the string that was entered using the keyboard over the input locale
  #  identifier (formerly called keyboard layout handles).
  #----------------------------------------------------------------------------
  def self.get_input_string
    result = ''
    # check every key
    ALL_KEYS.each {|key|
      # if repeated
      if self.repeat?(key)
        # get character from keyboard state
        c = self.get_character(key)
        # add character if there is a character
        result += c if c != ''
      end
    }
    # empty if result is empty
    return '' if result == ''
    # convert string from Unicode to UTF-8
    return self.unicode_to_utf8(result)
  end
  #----------------------------------------------------------------------------
  # unicode_to_utf8
  #  string - string in Unicode format
  #  Converts a string from Unicode format to UTF-8 format as RGSS does not
  #  support Unicode.
  #----------------------------------------------------------------------------
  def self.unicode_to_utf8(string)
    result = ''
    # L* format means a bunch of 4-byte wide-chars
    string.unpack('L*').each {|c|
      # characters under 0x80 are 1 byte characters
      if c < 0x0080
        result += c.chr
      # other characters under 0x800 are 2 byte characters
      elsif c < 0x0800
        result += (0xC0 | (c >> 6)).chr
        result += (0x80 | (c & 0x3F)).chr
      # other characters under 0x10000 are 3 byte characters
      elsif c < 0x10000
        result += (0xE0 | (c >> 12)).chr
        result += (0x80 | ((c >> 6) & 0x3F)).chr
        result += (0x80 | (c & 0x3F)).chr
      # other characters under 0x200000 are 4 byte characters
      elsif c < 0x200000
        result += (0xF0 | (c >> 18)).chr
        result += (0x80 | ((c >> 12) & 0x3F)).chr
        result += (0x80 | ((c >> 6) & 0x3F)).chr
        result += (0x80 | (c & 0x3F)).chr
      # other characters under 0x4000000 are 5 byte characters
      elsif c < 0x4000000
        result += (0xF8 | (c >> 24)).chr
        result += (0x80 | ((c >> 18) & 0x3F)).chr
        result += (0x80 | ((c >> 12) & 0x3F)).chr
        result += (0x80 | ((c >> 6) & 0x3F)).chr
        result += (0x80 | (c & 0x3F)).chr
      # other characters under 0x80000000 are 6 byte characters
      elsif c < 0x80000000
        result += (0xFC | (c >> 30)).chr
        result += (0x80 | ((c >> 24) & 0x3F)).chr
        result += (0x80 | ((c >> 18) & 0x3F)).chr
        result += (0x80 | ((c >> 12) & 0x3F)).chr
        result += (0x80 | ((c >> 6) & 0x3F)).chr
        result += (0x80 | (c & 0x3F)).chr
      end
    }
    return result
  end

end

end



Then the main RMX-OS script.  This took a lot more work, running, seeing where things crashed, and fixing them.  It's still a work in progress, but it's to the point now where it can load up the project, connect to a server, register and login, and pull up the first map, all successfully and without crashing.  Most of the actual MMO functionality has not been tested, however.

(Posted on Pastebin because of message character limit lengths.)

And then the Main script, adapted to work RGSS3-style:

Spoiler: ShowHide

#==============================================================================
# ** Main
#------------------------------------------------------------------------------
#  Runs the entire system.
#==============================================================================

ERROR_LOG_FILE = 'Error.log' # leave empty for no log

def mod_error(error)
  # load scripts
  scripts = load_data('Data/Scripts.rvdata2')
  bt = error.backtrace.clone
  # change backtrace display to show script names
  bt.each_index {|i| bt[i] = bt[i].sub(/\ASection(\d+)/) {scripts[$1.to_i][1]} + "\n"}
  # new error message
  message = error.message + "\n" + bt.join('')
  # write to file if file defined
  if ERROR_LOG_FILE != ''
    File.open(ERROR_LOG_FILE, 'a') {|f| f.write("#{Time.now.to_s}:\n#{message}\n")}
  end
  return message
end

begin
  # prepare for transition
  Graphics.freeze
  # activate the network
  $network = RMXOS::Network.new
  # call main for active scene
  begin
    rgss_main { SceneManager.run }
  rescue Exception => ex
    puts ex.backtrace
    raise
  end
  # disconnection
  $network.disconnect
  # fade out
  Graphics.transition(20)
  Graphics.update
rescue SyntaxError
  $!.message.sub!($!.message, mod_error($!))
  raise
rescue
  $!.message.sub!($!.message, mod_error($!))
  raise
ensure
  # disconnection
  $network.disconnect
end



That's what I've got so far.  I'm a pretty good programmer in general, but my Ruby experience is extremely limited and I'm kind of winging it here, so any feedback from more experienced scripters would be quite welcome.

Mod Edit: Please use [code][/code] tags when pasting code. Also fixed your URL. Our URL tags don't use quotation (") marks.

Blizzard

You'll likely need to do some work on the character classes for displaying other characters and the variables that are exchanged between players. Same goes for the party data that is being exchanged. The easiest way is to go class by class in RMX-OS, compare RMXP class to its RMVXA equivalent and do the changes.
Check out Daygames and our games:

King of Booze 2      King of Booze: Never Ever
Drinking Game for Android      Never have I ever for Android
Drinking Game for iOS      Never have I ever for iOS


Quote from: winkioI do not speak to bricks, either as individuals or in wall form.

Quote from: Barney StinsonWhen I get sad, I stop being sad and be awesome instead. True story.

Mason Wheeler

After some testing, saving seems to work just fine so far.  Just added a fix for teleports between maps, though, which was broken due to a different order of events between XP and VXAce.

Blizzard

Check out Daygames and our games:

King of Booze 2      King of Booze: Never Ever
Drinking Game for Android      Never have I ever for Android
Drinking Game for iOS      Never have I ever for iOS


Quote from: winkioI do not speak to bricks, either as individuals or in wall form.

Quote from: Barney StinsonWhen I get sad, I stop being sad and be awesome instead. True story.

whitespirits

It maybe better starting with is base and fixing it up or adding to it

http://www.tabernarpg.com/t218-netplay-ace-a-nova-base-para-netplay-s

I used RMXos a lot and it's major issue is when some 1 joins a map they become host and any other players then get a delay no matter what, made the combat impossible.

Blizzard

April 20, 2016, 01:36:00 am #5 Last Edit: April 20, 2016, 01:37:13 am by Blizzard
Ugh, Netplay. I don't think they used RMX-OS as a base if it's called Netplay, the notoriously buggy RMXP MMO engine which was actually one of the reasons I made RMX-OS.
Check out Daygames and our games:

King of Booze 2      King of Booze: Never Ever
Drinking Game for Android      Never have I ever for Android
Drinking Game for iOS      Never have I ever for iOS


Quote from: winkioI do not speak to bricks, either as individuals or in wall form.

Quote from: Barney StinsonWhen I get sad, I stop being sad and be awesome instead. True story.

whitespirits

ye I know what u mean blizz, I heard this 1 tho is remade from the base in a different way?

Blizzard

I'm not really sure. There are 2 main Netplay systems out there as far as I know. One is the old Netplay+ and the other is an upgraded version (v3.x or v4.x I think) where the server is written in C#. But they both still work on the same fundamentals that made Netplay so buggy (e.g. the way messages are formatted and processed when they are sent between server and client). It's true that they are different in general, but the basics of how they work are roughly the same. So in the end there is only Netplay+ and RMX-OS. All other systems are derived works. e.g. MRMX-OS is heavily based on RMX-OS while all Netplay+ variants are based on concepts the Netplay+ established. I'd say most people just take Netplay+ and say "Oh, the network code works so I'm not gonna touch that." even though the network code is the main problem in Netplay+ which nobody addressed since the first version. xD
Check out Daygames and our games:

King of Booze 2      King of Booze: Never Ever
Drinking Game for Android      Never have I ever for Android
Drinking Game for iOS      Never have I ever for iOS


Quote from: winkioI do not speak to bricks, either as individuals or in wall form.

Quote from: Barney StinsonWhen I get sad, I stop being sad and be awesome instead. True story.

whitespirits

Ah interesting man! I hope the conversion to VX fixes the delay issue lol