Show posts

This section allows you to view all posts made by this member. Note that you can only see posts made in areas you currently have access to.

Topics - Mason Wheeler

1
Buildings
Authors: Mason Wheeler
Version: 1.0
Type: Copyable buildings script
Key Term: Environment Add-on



Introduction

This is a script I wrote up to make it possible to create dynamic maps by defining custom regions in one map and copying them to another.  The custom regions are known as "buildings", because the basic idea is to make a customizable town, base, or similar, although the script can of course be used for things other than buildings.

It lets you define maps that contain buildings, and define which regions make up each building, and then copy them to your game maps using scripting.  The copying process will include any events located within the defined region.



Features


  • Create buildings on one map, then copy them dynamically to another

  • Copies events placed on the buildings

  • Built-in hook for registering copied events

  • New Game_Map#events_for_building_id method to conveniently find all events belonging to a certain building

  • RMX-OS compatible




Screenshots

None for the moment.


Demo

None for the moment


Script

Spoiler: ShowHide

#:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=
# RPG Maker VX Ace Buildings System
#------------------------------------------------------------------------------
# Author: Mason Wheeler
#------------------------------------------------------------------------------
#
# Allows a map to contain custom map elements that can be copied from another map
# and placed dynamically.  These elements are known in this script as "buildings",
# though they may look like any sort of map element.
#
#------------------------------------------------------------------------------
#
# LICENSE
#
# The contents of this script are used with permission, subject to
# the Mozilla Public License Version 1.1 (the "License"); you may
# not use this file except in compliance with the License. You may
# obtain a copy of the License at
# http://www.mozilla.org/MPL/MPL-1.1.html
#
# Software distributed under the License is distributed on an
# "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
# implied. See the License for the specific language governing
# rights and limitations under the License.
#
#------------------------------------------------------------------------------
#
# SETUP
#
# Any map that contains buildings should be named "Buildings".  You can place as
# many buildings into the map as you want.  You define the coordinates for your
# buildings in the Notes section of the map.  Each line should begin with a
# name, a colon, then 4 numbers separated by commas, denoting the x, y, width,
# and height values of a Rect that encloses the building, like so:
#
# Custom House 1:0,0,16,20
#
# Every building should have a name that is unique throughout the entire project.
# (Duplicate names will throw an error during setup.)
#
# Copying a building will copy any Events within the rect, with a few
# restrictions.  Bear in mind that each copied Event is a *copy*, that will end
# up somewhere other than the original map.  Therefore, nothing in the Rvent's
# script should reference elements of the original map, including other events
# or map coordinates.  If your building contains a Transfer Player command to an
# indoor location which has a door leading back out, this will need to save the
# player's coordinates to variables for the return teleport to use.
#
#------------------------------------------------------------------------------
#
# USAGE
#
# This script defines a global obect named $custom_buildings whose data property
# holds information about building locations.  To place buildings, call the
# add_building method on it.
#
# The method takes 5 arguments: (map, name, x, y, id).  The map value is the ID
# number of the map on which the building should be placed.  The name is a
# string, the name of the building.  It should match a building name as defined
# above.  Next are the X and Y coordinates on the destination map where the
# too-left corner of the building should begin.  The final value is a building
# ID, a custom tag that will be added to any Event copied with the building as
# its "building_id" property, which can be used in scripts to distinguish
# between two copies of the same original Event.  It should be unique within
# each map, and will raise an error if it is not.

# The building id value is also used in the remove_building method, to locate
# the buiding to be removed.
#
# $custom_buildings is scanned for buildings each time a map is setup.
# Therefore, changing the entry for a map while on that map will have no effect
# until the player leaves and returns to it or Buildings.map_refresh is called,
# which causes the map to reload itself.
#
# The Buildings.event_created(event) routine is provided as a convenient hook
# for other scripts to override.  Each time an event is copied as part of
# placing a building, this routine will be called, passing the new Game_Event
# object to it.
#
#------------------------------------------------------------------------------
#
# COMPATIBILITY
#
# This script can be used with RMX-OS.  It should be placed in the Materials
# section below the RMX-OS script, if applicable.
#
#:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=:=

class Building_Data
  attr_reader :data
  attr_reader :events
  attr_reader :coords
 
  def initialize(data, events, coords)
      @data = data
      @events = events
      @coords = coords
  end
end

class Scene_Title
  alias title_start start
  def start
    title_start
    Buildings.setup_houses
  end   
end

class Game_Event
  attr_accessor :building_id
  attr_reader :event
 
  alias ge_initialize initialize
  def initialize(map_id, event)
    ge_initialize(map_id, event)
    building_id = -1
  end
end

module DataManager
 
  class << self
    alias DM_save make_save_contents
    alias DM_load extract_save_contents
  end
 
  #--------------------------------------------------------------------------
  # * Create Save Contents
  #--------------------------------------------------------------------------
  def self.make_save_contents
    contents = DM_save
    contents[:custom_buildings] = $custom_buildings
    contents
  end
  #--------------------------------------------------------------------------
  # * Extract Save Contents
  #--------------------------------------------------------------------------
  def self.extract_save_contents(contents)
    DM_load(contents)
    $custom_buildings = contents[:custom_buildings]
  end
 
end

module Buildings

  class Custom_Buildings
    attr_accessor :data
   
    def initialize
      @data = {}
    end
   
    def add_building(map, name, x, y, id)
      mapdata = @data[map]
      if mapdata.nil?
        mapdata = {}
        @data[map] = mapdata
      end
      raise "Duplicate ID #{id} on map # #{map}" if mapdata.has_key?(id)
      mapdata[id] = [name, x, y]
    end
   
    def remove_building(map, id)
      mapdata = @data[map]
      unless mapdata.nil?
        mapdata.delete(id)
        @data.delete(map) if mapdata.empty?
      end
    end
  end
 
  def self.setup_houses
    $houses = {}
    houses = $data_mapinfos.find_all{|id, map| map.name == "Buildings"}.map{|a| a[0]}
    houses.each do |id|
      mapinfo = load_data(sprintf("Data/Map%03d.rvdata2", id))
      mapinfo.note.split('\n').each do |line|
        name, values = line.split(':')
        coords = values.split(',').map{|v| v.to_i}.to_a
        rect = Rect.new(coords[0], coords[1], coords[2], coords[3])
        raise "Duplite building name #{name}" if $houses[name]
        $houses[name] = Building_Data.new(mapinfo.data, mapinfo.events, rect)
      end
    end
  end
 
  def self._blit_house(map, house_id, left, top, building_id)
    house = $houses[house_id]
    data = house.data
    coords = house.coords
    coords.height.times do |y|
      y2 = y + top + coords.y
      coords.width.times do |x|
        x2 = x + left + coords.x
        4.times{|z| map.data[x2, y2, z] = data[x, y, z]}
      end
    end
   
    house.events.each do |i, event|
      if (event.x >= coords.x) && (event.x < coords.x + coords.width) && (event.y >= coords.y) && (event.y < coords.y + coords.height)
        _blit_house_event(map, i, event, left, top, building_id, coords)
      end
    end
  end

  def self._blit_house_event(map, i, event, left, top, building_id, coords)
    event_id = map.events.size + 1
    newEvent = Game_Event.new(map.map_id, event)
    newEvent.moveto(event.x + left - coords.x, event.y + top - coords.y)
    newEvent.id = event_id
    newEvent.building_id = building_id
    map.events[event_id] = newEvent
    event_created(newEvent)
  end

  def self.map_refresh
    $game_map.setup($game_map.map_id)
    $game_player.center($game_player.x, $game_player.y)
    $game_player.make_encounter_count
  end

  def self.event_created(event) #override this as needed
  end
end

class Game_Map
  $custom_buildings = Buildings::Custom_Buildings.new
 
  alias gm_setup setup
  def setup(map_id)
    gm_setup(map_id)
    cust = $custom_buildings.data[map_id]
    if cust
      cust.each do |id, value|
        map, left, top = value
        Buildings._blit_house(self, map, left, top, id)
      end
    end
  end
end

def this_event
  $game_map.events[$game_map.interpreter.event_id]
end

if defined? RMXOS
 
  class Scene_Servers
    alias servers_setup setup_scene
    def setup_scene
      servers_setup
      Buildings.setup_houses
    end   
  end
 
  module RMXOS
    module Options
      SAVE_CONTAINERS.push('$custom_buildings')
      SAVE_DATA[Buildings::Custom_Buildings] = ['@data']
    end
  end
 
end



Instructions

Pretty straightforward, just see the documentation at the top of the script.


Compatibility

This script will work with RMX-OS (VXAce version).  Place it after the RMX-OS script, if applicable.


Credits and Thanks

(This is original work)



Author's Notes

Enjoy and report any bugs or suggestions here. :)



License

This is released under the MPL license, which essentially says that you may freely use this script with any project, including commercial projects.  However, if you modify this script, for example to fix bugs or add new features, you are required to publish your modified version under the same license.  (The rest of your project is not affected by this license, only the script itself.)
2
RPG Maker Scripts / [RMX-OS] Simple instance maps
April 03, 2016, 08:47:04 am
So after looking over a few partial proposals on here, I decided to try and do instance maps myself.  It turned out to be surprisingly easy and not require any new client-side messages or special scripting.  (For ordinary gameplay at least.  Administration and visiting someone else's instance requires scripting to send special messages.)  All it requires is a new database table to hold a list of IDs of maps that should be shared (ie. the RMX-OS default behavior.  This script changes it so all maps are instance maps by default.)  Everything needed for setup and implementation is explained in the header comment block, except the obvious observation that it needs to be added to EXTENSIONS on cfg.ini in order for it to work.

Spoiler: ShowHide
Code: ruby

#======================================================================
# Instance Maps extension.  This script allows the server to have maps
# that aren't shared between players, and in fact makes this the default.
# It requires a new table in the database that holds a list of maps that
# *should* be shared, which can be set up with the following SQL command:
#
# CREATE TABLE `shared_maps` (
# `map_id` int unsigned NOT NULL,
# PRIMARY KEY (`map_id`)
# ) ENGINE = InnoDB;
#
# Any map not on this list will be treated as an instance map.  The
# extension handles this completely transparently by intercepting and
# modifying MEN (Map Enter) messages before they can be processed by
# Client#check_game.  This means that no special scripts or client-side
# code of any kind is needed to enable instance maps.
#
# The shared_maps database table can be safely modified while the server
# is running.  Once it has been modified, the admin will need to send a
# "IMAP" message to the server, with no parameters.  This will cause the
# extension to reload its shared maps data.
#
# It's also possible to visit someone else's instance by sending a MEN
# message with the appropriate ID value, which can be calculated
# client-side from the map ID and user ID.
#
#======================================================================


module RMXOS

#------------------------------------------------------------------
# Passes the extension's main module to RMX-OS on the top
# level so it can handle this extension.
# Returns: Module of this extension for update.
#------------------------------------------------------------------
def self.load_current_extension
return InstanceMaps
end

end

#======================================================================
# module InstanceMaps
#======================================================================

module InstanceMaps

# extension version
VERSION = 1.0
# required RMX-OS version
RMXOS_VERSION = 2.0
# whether the server should update this extension in an individual thread or not
SERVER_THREAD = true
# the extension's name/identifier
IDENTIFIER = 'Instance Maps'

#------------------------------------------------------------------
# Initializes the extension (i.e. instantiation of classes).
#------------------------------------------------------------------
def self.initialize
# create mutex
@mutex = Mutex.new
end

#------------------------------------------------------------------
# Gets the local extension mutex.
#------------------------------------------------------------------
def self.mutex
return @mutex
end

#------------------------------------------------------------------
# Shouldn't need to do this in a thread, but it has to happen after
# the server is created, and extensions are initialized before that
# point
#------------------------------------------------------------------
def self.main
self._load_shared_maps
end

#------------------------------------------------------------------
# Handles updating from a client.
# client - Client instance (from Client.rb)
# Returns: Whether to stop check the message or not.
#------------------------------------------------------------------
def self.client_update(client)
case client.message
when /\AMEN\t(.+)/
mapid = (
#======================================================================
# Instance Maps extension.  This script allows the server to have maps
# that aren't shared between players, and in fact makes this the default.
# It requires a new table in the database that holds a list of maps that
# *should* be shared, which can be set up with the following SQL command:
#
# CREATE TABLE `shared_maps` (
# `map_id` int unsigned NOT NULL,
# PRIMARY KEY (`map_id`)
# ) ENGINE = InnoDB;
#
# Any map not on this list will be treated as an instance map.  The
# extension handles this completely transparently by intercepting and
# modifying MEN (Map Enter) messages before they can be processed by
# Client#check_game.  This means that no special scripts or client-side
# code of any kind is needed to enable instance maps.
#
# The shared_maps database table can be safely modified while the server
# is running.  Once it has been modified, the admin will need to send a
# "IMAP" message to the server, with no parameters.  This will cause the
# extension to reload its shared maps data.
#
# It's also possible to visit someone else's instance by sending a MEN
# message with the appropriate ID value, which can be calculated
# client-side from the map ID and user ID.
#
#======================================================================


module RMXOS

#------------------------------------------------------------------
# Passes the extension's main module to RMX-OS on the top
# level so it can handle this extension.
# Returns: Module of this extension for update.
#------------------------------------------------------------------
def self.load_current_extension
return InstanceMaps
end

end

#======================================================================
# module InstanceMaps
#======================================================================

module InstanceMaps

# extension version
VERSION = 1.0
# required RMX-OS version
RMXOS_VERSION = 2.0
# whether the server should update this extension in an individual thread or not
SERVER_THREAD = true
# the extension's name/identifier
IDENTIFIER = 'Instance Maps'

#------------------------------------------------------------------
# Initializes the extension (i.e. instantiation of classes).
#------------------------------------------------------------------
def self.initialize
# create mutex
@mutex = Mutex.new
end

#------------------------------------------------------------------
# Gets the local extension mutex.
#------------------------------------------------------------------
def self.mutex
return @mutex
end

#------------------------------------------------------------------
# Shouldn't need to do this in a thread, but it has to happen after
# the server is created, and extensions are initialized before that
# point
#------------------------------------------------------------------
def self.main
self._load_shared_maps
end

#------------------------------------------------------------------
# Handles updating from a client.
# client - Client instance (from Client.rb)
# Returns: Whether to stop check the message or not.
#------------------------------------------------------------------
def self.client_update(client)
case client.message
when /\AMEN\t(.+)/
mapid = ($1.to_i)
return false if mapid > 1000
is_shared = @sharedmaps[mapid]
unless is_shared
mapid += client.player.user_id * 1000
client.message = "MEN\t#{mapid}"
end
when /\AIMAP\Z/
self._load_shared_maps
return true
end
return false
end

def self._load_shared_maps
@sharedmaps = {}
sql = RMXOS::SQL.new(RMXOS.server.options)
dataset = sql.query('select map_id from shared_maps')
dataset.num_rows.times {
hash = dataset.fetch_hash
@sharedmaps[hash['map_id']] = true
}
end

end
.to_i)
return false if mapid > 1000
is_shared = @sharedmaps[mapid]
unless is_shared
mapid += client.player.user_id * 1000
client.message = "MEN\t#{mapid}"
end
when /\AIMAP\Z/
self._load_shared_maps
return true
end
return false
end

def self._load_shared_maps
@sharedmaps = {}
sql = RMXOS::SQL.new(RMXOS.server.options)
dataset = sql.query('select map_id from shared_maps')
dataset.num_rows.times {
hash = dataset.fetch_hash
@sharedmaps[hash['map_id']] = true
}
end

end


Any feedback is welcome, of course.

Edit: Fixed an error with recursive mutex locking.  Also added support for visiting someone else's instance.
3
Does anyone have a script that will let you create a new Actor from a Class, who's not predefined in the Database?  Anyone who's recruited new random party members in a Disgaea game will understand what I'm looking for and why it's useful.
4
Development Tools / [Boo] Project scanner for XP/VXA
March 28, 2016, 02:29:16 pm
This is not strictly a script, but you don't have any place for tools, so this is the closest match.

It's a tool that scans a project, looking over its events for various criteria.  If you've ever wondered where the Transfer Player that gets you to a certain map is, or how to set a certain quest-relevant variable, this tool is designed to answer such questions, either for digging around in your own work that's gotten too big for you to hold all in your head, or reverse-engineering another game for help on a quest you're stuck on.

It can be downloaded at https://github.com/masonwheeler/RMXP-Scanner/releases and documentation is available on the main page.  It supports XP and VXAce, but not VX because I don't have that version to figure out how to scan it.

Enjoy!
5
RPG Maker Scripts / RMX-OS port to VXAce
March 28, 2016, 06:57:07 am
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.