mirror of
https://github.com/infinitefusion/infinitefusion-e18.git
synced 2025-12-06 06:01:46 +00:00
Yet more script rearranging
This commit is contained in:
381
Data/Scripts/001_Technical/005_PluginManager.rb
Normal file
381
Data/Scripts/001_Technical/005_PluginManager.rb
Normal file
@@ -0,0 +1,381 @@
|
||||
#==============================================================================#
|
||||
# Plugin Manager #
|
||||
# by Marin #
|
||||
#------------------------------------------------------------------------------#
|
||||
# Provides a simple interface that allows plugins to require dependencies #
|
||||
# at specific versions, and to specify incompatibilities between plugins. #
|
||||
#------------------------------------------------------------------------------#
|
||||
# Usage: #
|
||||
# #
|
||||
# A Pokémon Essentials plugin should register itself using the PluginManager. #
|
||||
# The simplest way to do so, for a plugin without dependencies, is as follows: #
|
||||
# #
|
||||
# PluginManager.register({ #
|
||||
# :name => "Basic Plugin", #
|
||||
# :version => "1.0", #
|
||||
# :link => "https://reliccastle.com/link-to-the-plugin/", #
|
||||
# :credits => "Marin" #
|
||||
# }) #
|
||||
# #
|
||||
# The link portion here is optional, but recommended. This will be shown in #
|
||||
# the error message if the PluginManager detects that this plugin needs to be #
|
||||
# updated. #
|
||||
# #
|
||||
# A plugin's version is typically in the format X.Y.Z, but the number of #
|
||||
# digits does not matter. You can also use Xa, Xb, Xc, Ya, etc. #
|
||||
# What matters is that you use it consistently, so that it can be compared. #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# Now let's say we create a new plugin titled "Simple Extension", which #
|
||||
# requires our previously created "Basic Plugin" to work. #
|
||||
# #
|
||||
# PluginManager.register({ #
|
||||
# :name => "Simple Extension", #
|
||||
# :version => "1.0", #
|
||||
# :link => "https://reliccastle.com/link-to-the-plugin/", #
|
||||
# :credits => ["Marin", "Maruno"], #
|
||||
# :dependencies => ["Basic Plugin"] #
|
||||
# }) #
|
||||
# #
|
||||
# This plugin has two credits as an array, instead of one string. Furthermore, #
|
||||
# this code will ensure that "Basic Plugin" is installed, ignoring its #
|
||||
# version. If you have only one dependency, you can omit the array brackets #
|
||||
# like so: #
|
||||
# #
|
||||
# :dependencies => "Basic Plugin" #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# To require a minimum version of a dependency plugin, you should turn the #
|
||||
# dependency's name into an array which contains the name and the version #
|
||||
# (both as strings). For example, to require "Basic Plugin" version 1.2 or #
|
||||
# higher, you would write: #
|
||||
# #
|
||||
# PluginManager.register({ #
|
||||
# :name => "Simple Extension", #
|
||||
# :version => "1.0", #
|
||||
# :link => "https://reliccastle.com/link-to-the-plugin/", #
|
||||
# :credits => "Marin", #
|
||||
# :dependencies => [ #
|
||||
# ["Basic Plugin", "1.2"] #
|
||||
# ] #
|
||||
# }) #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# To require a specific version (no higher and no lower) of a dependency #
|
||||
# plugin, you should add the :exact flag as the first thing in the array for #
|
||||
# that dependency: #
|
||||
# #
|
||||
# PluginManager.register({ #
|
||||
# :name => "Simple Extension", #
|
||||
# :version => "1.0", #
|
||||
# :link => "https://reliccastle.com/link-to-the-plugin/", #
|
||||
# :credits => "Marin", #
|
||||
# :dependencies => [ #
|
||||
# [:exact, "Basic Plugin", "1.2"] #
|
||||
# ] #
|
||||
# }) #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# If your plugin is known to be incompatible with another plugin, you should #
|
||||
# list that other plugin as such. Only one of the two plugins needs to list #
|
||||
# that it is incompatible with the other. #
|
||||
# #
|
||||
# PluginManager.register({ #
|
||||
# :name => "QoL Improvements", #
|
||||
# :version => "1.0", #
|
||||
# :link => "https://reliccastle.com/link-to-the-plugin/", #
|
||||
# :credits => "Marin", #
|
||||
# :incompatibilities => [ #
|
||||
# "Simple Extension" #
|
||||
# ] #
|
||||
# }) #
|
||||
# #
|
||||
# #
|
||||
# #
|
||||
# If your plugin can work without another plugin, but is known to be #
|
||||
# incompatible with an old version of that other plugin, you should list it as #
|
||||
# an optional dependency. If that other plugin is present in a game, then this #
|
||||
# optional dependency will ensure it meets the minimum version required for #
|
||||
# your plugin. Write it in the same way as any other dependency as described #
|
||||
# above, but use the :optional flag instead. #
|
||||
# You do not need to list a plugin as an optional dependency at all if all #
|
||||
# versions of that other plugin are compatible with your plugin. #
|
||||
# #
|
||||
# PluginManager.register({ #
|
||||
# :name => "Other Plugin", #
|
||||
# :version => "1.0", #
|
||||
# :link => "https://reliccastle.com/link-to-the-plugin/", #
|
||||
# :credits => "Marin", #
|
||||
# :dependencies => [ #
|
||||
# [:optional, "QoL Improvements", "1.1"] #
|
||||
# ] #
|
||||
# }) #
|
||||
# #
|
||||
# The :optional_exact flag is a combination of :optional and :exact. #
|
||||
#------------------------------------------------------------------------------#
|
||||
# Please give credit when using this. #
|
||||
#==============================================================================#
|
||||
|
||||
module PluginManager
|
||||
# Win32API MessageBox function for custom errors.
|
||||
# MBOX = Win32API.new('user32', 'MessageBox', ['I','P','P','I'], 'I')
|
||||
# Holds all registered plugin data.
|
||||
@@Plugins = {}
|
||||
|
||||
# Registers a plugin and tests its dependencies and incompatibilities.
|
||||
def self.register(options)
|
||||
name = nil
|
||||
version = nil
|
||||
link = nil
|
||||
dependencies = nil
|
||||
incompats = nil
|
||||
credits = []
|
||||
order = [:name, :version, :link, :dependencies, :incompatibilities, :credits]
|
||||
# Ensure it first reads the plugin's name, which is used in error reporting,
|
||||
# by sorting the keys
|
||||
keys = options.keys.sort do |a, b|
|
||||
idx_a = order.index(a)
|
||||
idx_a = order.size if idx_a == -1
|
||||
idx_b = order.index(b)
|
||||
idx_b = order.size if idx_b == -1
|
||||
next idx_a <=> idx_b
|
||||
end
|
||||
for key in keys
|
||||
value = options[key]
|
||||
case key
|
||||
when :name # Plugin name
|
||||
if nil_or_empty?(value)
|
||||
self.error("Plugin name must be a non-empty string.")
|
||||
end
|
||||
if !@@Plugins[value].nil?
|
||||
self.error("A plugin called '#{value}' already exists.")
|
||||
end
|
||||
name = value
|
||||
when :version # Plugin version
|
||||
if nil_or_empty?(value)
|
||||
self.error("Plugin version must be a string.")
|
||||
end
|
||||
version = value
|
||||
when :link # Plugin website
|
||||
if nil_or_empty?(value)
|
||||
self.error("Plugin link must be a non-empty string.")
|
||||
end
|
||||
link = value
|
||||
when :dependencies # Plugin dependencies
|
||||
dependencies = value
|
||||
dependencies = [dependencies] if !dependencies.is_a?(Array) || !dependencies[0].is_a?(Array)
|
||||
for dep in value
|
||||
if dep.is_a?(String) # "plugin name"
|
||||
if !self.installed?(dep)
|
||||
self.error("Plugin '#{name}' requires plugin '#{dep}' to be installed above it.")
|
||||
end
|
||||
elsif dep.is_a?(Array)
|
||||
case dep.size
|
||||
when 1 # ["plugin name"]
|
||||
if dep[0].is_a?(String)
|
||||
dep_name = dep[0]
|
||||
if !self.installed?(dep_name)
|
||||
self.error("Plugin '#{name}' requires plugin '#{dep_name}' to be installed above it.")
|
||||
end
|
||||
else
|
||||
self.error("Expected the plugin name as a string, but got #{dep[0].inspect}.")
|
||||
end
|
||||
when 2 # ["plugin name", "version"]
|
||||
if dep[0].is_a?(Symbol)
|
||||
self.error("A plugin version comparator symbol was given but no version was given.")
|
||||
elsif dep[0].is_a?(String) && dep[1].is_a?(String)
|
||||
dep_name = dep[0]
|
||||
dep_version = dep[1]
|
||||
next if self.installed?(dep_name, dep_version)
|
||||
if self.installed?(dep_name) # Have plugin but lower version
|
||||
msg = "Plugin '#{name}' requires plugin '#{dep_name}' version #{dep_version} or higher, " +
|
||||
"but the installed version is #{self.version(dep_name)}."
|
||||
if dep_link = self.link(dep_name)
|
||||
msg += "\r\nCheck #{dep_link} for an update to plugin '#{dep_name}'."
|
||||
end
|
||||
self.error(msg)
|
||||
else # Don't have plugin
|
||||
self.error("Plugin '#{name}' requires plugin '#{dep_name}' version #{dep_version} " +
|
||||
"or higher to be installed above it.")
|
||||
end
|
||||
end
|
||||
when 3 # [:optional/:exact/:optional_exact, "plugin name", "version"]
|
||||
if !dep[0].is_a?(Symbol)
|
||||
self.error("Expected first dependency argument to be a symbol, but got #{dep[0].inspect}.")
|
||||
end
|
||||
if !dep[1].is_a?(String)
|
||||
self.error("Expected second dependency argument to be a plugin name, but got #{dep[1].inspect}.")
|
||||
end
|
||||
if !dep[2].is_a?(String)
|
||||
self.error("Expected third dependency argument to be the plugin version, but got #{dep[2].inspect}.")
|
||||
end
|
||||
dep_arg = dep[0]
|
||||
dep_name = dep[1]
|
||||
dep_version = dep[2]
|
||||
optional = false
|
||||
exact = false
|
||||
case def_arg
|
||||
when :optional
|
||||
optional = true
|
||||
when :exact
|
||||
exact = true
|
||||
when :optional_exact
|
||||
optional = true
|
||||
exact = true
|
||||
else
|
||||
self.error("Expected first dependency argument to be one of " +
|
||||
":optional, :exact or :optional_exact, but got #{dep_arg.inspect}.")
|
||||
end
|
||||
if optional
|
||||
if self.installed?(dep_name) && # Have plugin but lower version
|
||||
!self.installed?(dep_name, dep_version, exact)
|
||||
msg = "Plugin '#{name}' requires plugin '#{dep_name}', if installed, to be version #{dep_version}"
|
||||
msg << " or higher" if !exact
|
||||
msg << ", but the installed version was #{self.version(dep_name)}."
|
||||
if dep_link = self.link(dep_name)
|
||||
msg << "\r\nCheck #{dep_link} for an update to plugin '#{dep_name}'."
|
||||
end
|
||||
self.error(msg)
|
||||
end
|
||||
elsif !self.installed?(dep_name, dep_version, exact)
|
||||
if self.installed?(dep_name) # Have plugin but lower version
|
||||
msg = "Plugin '#{name}' requires plugin '#{dep_name}' to be version #{dep_version}"
|
||||
msg << " or later" if !exact
|
||||
msg << ", but the installed version was #{self.version(dep_name)}."
|
||||
if dep_link = self.link(dep_name)
|
||||
msg << "\r\nCheck #{dep_link} for an update to plugin '#{dep_name}'."
|
||||
end
|
||||
self.error(msg)
|
||||
else # Don't have plugin
|
||||
msg = "Plugin '#{name}' requires plugin '#{dep_name}' version #{dep_version} "
|
||||
msg << "or later" if !exact
|
||||
msg << "to be installed above it."
|
||||
self.error(msg)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
when :incompatibilities # Plugin incompatibilities
|
||||
incompats = value
|
||||
incompats = [incompats] if !incompats.is_a?(Array)
|
||||
for incompat in incompats
|
||||
if self.installed?(incompat)
|
||||
self.error("Plugin '#{name}' is incompatible with '#{incompat}'. " +
|
||||
"They cannot both be used at the same time.")
|
||||
end
|
||||
end
|
||||
when :credits # Plugin credits
|
||||
value = [value] if value.is_a?(String)
|
||||
if value.is_a?(Array)
|
||||
for entry in value
|
||||
if !entry.is_a?(String)
|
||||
self.error("Plugin '#{name}'s credits array contains a non-string value.")
|
||||
else
|
||||
credits << entry
|
||||
end
|
||||
end
|
||||
else
|
||||
self.error("Plugin '#{name}'s credits field must contain a string, or a string array.")
|
||||
end
|
||||
else
|
||||
self.error("Invalid plugin registry key '#{key}'.")
|
||||
end
|
||||
end
|
||||
for plugin in @@Plugins.values
|
||||
if plugin[:incompatibilities] && plugin[:incompatibilities].include?(name)
|
||||
self.error("Plugin '#{plugin[:name]}' is incompatible with '#{name}'. " +
|
||||
"They cannot both be used at the same time.")
|
||||
end
|
||||
end
|
||||
# Add plugin to class variable
|
||||
@@Plugins[name] = {
|
||||
:name => name,
|
||||
:version => version,
|
||||
:link => link,
|
||||
:dependencies => dependencies,
|
||||
:incompatibilities => incompats,
|
||||
:credits => credits
|
||||
}
|
||||
end
|
||||
|
||||
# Throws a pure error message without stack trace or any other useless info.
|
||||
def self.error(msg)
|
||||
Graphics.update
|
||||
t = Thread.new do
|
||||
#MBOX.call(Win32API.pbFindRgssWindow, msg, "Plugin Error", 0x10)
|
||||
p "Plugin Error:\n#{msg}"
|
||||
Thread.exit
|
||||
end
|
||||
while t.status
|
||||
Graphics.update
|
||||
end
|
||||
Kernel.exit! true
|
||||
end
|
||||
|
||||
# Returns true if the specified plugin is installed.
|
||||
# If the version is specified, this version is taken into account.
|
||||
# If mustequal is true, the version must be a match with the specified version.
|
||||
def self.installed?(plugin_name, plugin_version = nil, mustequal = false)
|
||||
plugin = @@Plugins[plugin_name]
|
||||
return false if plugin.nil?
|
||||
return true if plugin_version.nil?
|
||||
comparison = compare_versions(plugin[:version], plugin_version)
|
||||
return true if !mustequal && comparison >= 0
|
||||
return true if mustequal && comparison == 0
|
||||
end
|
||||
|
||||
# Returns the string names of all installed plugins.
|
||||
def self.plugins
|
||||
return @@Plugins.keys
|
||||
end
|
||||
|
||||
# Returns the installed version of the specified plugin.
|
||||
def self.version(plugin_name)
|
||||
return if !installed?(plugin_name)
|
||||
return @@Plugins[plugin_name][:version]
|
||||
end
|
||||
|
||||
# Returns the link of the specified plugin.
|
||||
def self.link(plugin_name)
|
||||
return if !installed?(plugin_name)
|
||||
return @@Plugins[plugin_name][:link]
|
||||
end
|
||||
|
||||
# Returns the credits of the specified plugin.
|
||||
def self.credits(plugin_name)
|
||||
return if !installed?(plugin_name)
|
||||
return @@Plugins[plugin_name][:credits]
|
||||
end
|
||||
|
||||
# Compares two versions given in string form. v1 should be the plugin version
|
||||
# you actually have, and v2 should be the minimum/desired plugin version.
|
||||
# Return values:
|
||||
# 1 if v1 is higher than v2
|
||||
# 0 if v1 is equal to v2
|
||||
# -1 if v1 is lower than v2
|
||||
def self.compare_versions(v1, v2)
|
||||
d1 = v1.split("")
|
||||
d1.insert(0, "0") if d1[0] == "." # Turn ".123" into "0.123"
|
||||
while d1[-1] == "."; d1 = d1[0..-2]; end # Turn "123." into "123"
|
||||
d2 = v2.split("")
|
||||
d2.insert(0, "0") if d2[0] == "." # Turn ".123" into "0.123"
|
||||
while d2[-1] == "."; d2 = d2[0..-2]; end # Turn "123." into "123"
|
||||
for i in 0...[d1.size, d2.size].max # Compare each digit in turn
|
||||
c1 = d1[i]
|
||||
c2 = d2[i]
|
||||
if c1
|
||||
return 1 if !c2
|
||||
return 1 if c1.to_i(16) > c2.to_i(16)
|
||||
return -1 if c1.to_i(16) < c2.to_i(16)
|
||||
else
|
||||
return -1 if c2
|
||||
end
|
||||
end
|
||||
return 0
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user