Vendetta/design

From Quasar-RCE
Jump to navigationJump to search

Creating LME plugins

When designing plugins for an LME, there are a small number of requirements that must be adhered to. LME plugins require a Declaration of data, which allows Neoloader to determine what a mod is and what it requires to launch. Additionally, LME plugins can be executed at arbitrary times, which changes how certain functions within Vendetta Online work. These changes are listed below:

Area of incompatibility Why the issue exists How to fix or work around
File access During the standard loader's execution, loadfile/dofile will prefix the active working directory to any path provided. During Neoloader's execution, however, because it is loading using the interface system, it does not have this alteration provided. Plugins that expect prepended paths will obviously break when loaded during the Init loading phase. This also occurs if code is executed after the 'PLUGINS_LOADED' event. Replace dofile with lib.resolve_file(), which is capable of testing for multiple locations of where to find a file.

note: Some functionality of dofile is not possible with lib.resolve_file(), such as passing arguments as local values. You can manually locate the correct path to files using lib.find_file() instead, and dofile() the result.

Creating Global Variables Plugins typically expect global values to be creatable when they run. While it is reasonable to expect LME plugins to also have global creation access, it is possible for Neoloader to delay a plugin from activating until after the PLUGINS_LOADED event occurs, depending on the configuration of the user's system and dependency fulfillment. While this should rarely, if ever, occur, assumptions should never be made about a user's given configuration. Either test for globals to be locked (lib.get_gstate().statelock == true) or use Declare() when creating globals. The latter function is part of the base game, and is not reliant on Neoloader.
Interface Incompatibility This is less likely to be an issue for most plugin authors, but Neoloader does make it much easier to register custom interfaces, which may not provide 1 to 1 compatibility with the DefaultUI (especially when it comes to IUP structures) Test for the default interface to be loaded (lib.get_gstate().ifmgr == "vo-if") or check specifically for the IUP structures to be accessed. Interfaces may provide some form of compatibility layer, too, so it may be prudent to check for commonplace APIs or Neoloader libraries that can solve this problem.

Function Libraries

Plugins have historically been designed to be Monolithic, containing all functionality not provided by the base game. This has lead to sometimes duplicated code and plugins that don't mesh well with each other. However, Neoloader aims to provide a standardized system for facilitating communication between different plugins through the public function class. When a Neoloader plugin is registered, it is given a table where it can store public functions other plugins can then call. Multiple versions of the same plugin can even be loaded at the same time, as they each get their own table. Through this, function libraries can provide common code usable between many different plugins easily.

There are two officially recognised styles that this type of plugin can take: Distributable and Standalone.

Distributable library The Distributable Library is designed to be as self-contained as possible, and is distributed alongside plugins that rely on them. This makes more sense for small libraries focused on specific functionality. Distributable libraries cannot assume their path; their declaration file must not provide a path, and if paths are needed for files, they must be found through lib.get_path. The declaration file is registered by the plugin that is distributing the library, so it cannot be embedded inside the plugin's code. Example: Babel (todo: link where its being used in distributable form)
Standalone library The Standalone Library is designed to be downloaded by the end user and is loaded as a plugin. This makes more sense for major libraries containing many files and/or a wide range of functionality. Standalone libraries are declared and defined just like normal plugins. Example: Resound

There is no reason a library cannot have both Distributable and Standalone releases, too, for instance if a library has a "lite" function set that is easily downloaded due to its small size, with an optional heavier function library that has more UI components, better on-demand fulfillment of plugin conflict resolution, or other variations. Alternatively, both could be offered while being the exact same, allowing the user to pick and choose how to manage the plugins directly.

In the end, it is up to both the library designer and the implementor to properly decide on best practices for determining which should be used and how to use them.

Function Libraries will use the lib.set_class function to store their table of public functions and data, and plugins reliant on this data will use either lib.execute or lib.get_class to retrieve this data. Please refer to the LME API document to determine how to use each and which will serve you best.

Version control

As mentioned in prior sections, plugins can (and should) have version numbers associated with them. Neoloader is capable of processing any version string provided the following rules are met:

  1. Versions are identifiable by a numeric value
  2. Subversions are seperated by a non-numeric value
  3. A hyphen is exclusively used to seperate a version string from other metadata

For example, take the semantic versioning string "1.0.2 -beta". This would break down into the following relevant data:

Major Minor Patch Metadata
1 0 2 beta

This same data would be obtained if someone provided the version string "v1.0r2 -beta": in this case, the preceding v is dropped, 1 and 0 are seperated by a non-numeric value, and 0 and 2 are also seperated by a non-numeric value. As long as a hyphen is not used, Neoloader will be able to process the version string appropriately.

Obtaining a tabled breakdown of a version string, and comparing between two version strings, are done by the lib.get_whole_ver and lib.compare_sem_ver functions in the API respectively.

While plugins can call on specific versions of other plugins through the various functions provided by the LME API, it is important to note that version zero is mostly reserved; when a plugin asks for the class table of a plugin and provides "0" as the version to call on, Neoloader will retrieve and pass on the class of the plugin that has the highest version present (provided it is set as loaded, of course). Version "0.0" would not trigger this, as it would not be version "0", nor would version "0 -dist".

It is suggested that versions provided to Neoloader be in Semantic Versioning - this is not for Neoloader's sake, but for plugin authors and end-users, so they can better recognise differences in provided plugins. However, in the end, as long as plugins use the same formatting style between different versions of the same plugin, then Neoloader will be able to process them correctly.

Dependency Declaration

Neoloader allows plugins to declare dependencies on other plugins, and will determine the appropriate time to load them as a result. Dependencies can be declared two different ways; the resultant plugin will often define something as a Hard or a Soft dependency based on which method is used.

Hard dependency This plugin requires this dependency to be completely fulfilled before it can run.
Soft dependency This plugin requires this dependency for additional functionality

The first method, Hard Dependency, will be much more common. You declare hard dependencies directly in a plugin's declaration file, and the plugin itself will never load unless they are fulfilled. Neoloader will figure out what order plugins should load in to have their dependencies fulfilled, as long as the dependencies are present and set to load.

The second method, Soft Dependency, is meant to delay specific functions from running until a dependency is fulfilled. This is best for when you want to optionally provide functionality based on the presence or lack of a library, but the functionality provided is not necessary for the working state of your plugin. Soft Dependencies are set through using the lib.require function.

Converting existing plugins

Due to limitations with the Vendetta Online sandbox, Neoloader is incapable of managing non-LME plugins. To this end, users can use the Neopatcher tool, while plugin developers not interested in Neoloader's full features are recommended to simply add the following to the top of their main.lua file:

--[[
[modreg]
id=plugin_id
version=1.0.0
name=plugin public name
author=plugin author
]]--

if type(lib) == "table" and lib[0] == "LME" then
	local plugin_path = "plugins/your path/main.lua"
	
	lib.register(plugin_path)
	
	if not lib.is_ready(plugin_path) then
		return --the plugin is disabled, stop here
	end
	
	--[[
		Any LME-specific code here
	]]--
end

--[[
	Your plugin here
]]--

By editing the inline INI data and the plugin_path variable, the plugin will be recognized and manageable by Neoloader, without requiring any significant modifications by authors.