[RMX-OS] Simple instance maps

Started by Mason Wheeler, April 03, 2016, 08:47:04 am

Previous topic - Next topic

Mason Wheeler

April 03, 2016, 08:47:04 am Last Edit: April 03, 2016, 12:17:43 pm by Mason Wheeler
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.