Skip to content

Developing a plugin

David edited this page Feb 6, 2022 · 12 revisions

๐ŸŽ‰ Start here

Thanks for checking out MPR! Use these docs to develop your own Menu Plugin.

โš ๏ธ NOTE: These specs are prone to change until a 1.0 release of MPR! โš ๏ธ

All plugins are loaded from the garrysmod/lua/menu_plugins folder.
Plugins are run alphabetically based on their ID. This occurs right before the main menu is loaded.
Consider using the MenuVGUIReady hook and/or check IsValid(pnlMainMenu) if you need to make modifications to it. (This might not work if the user has a custom main menu installed.)

โฌ†๏ธ Updating a legacy plugin

All plugins that worked with the legacy Menu Plugins repo should work just the same here with zero modifications. However, you can upgrade your plugin to benefit from the extra features brought along by MPR. Redux plugins require a manifest and optionally a config. Besides that, you'll need to replace some menup library calls with their new counterparts.

๐Ÿ“ The manifest & Config

All redux plugins need to define a manifest in order to be recognized as a valid redux plugin. Furthermore, you can define a config with various types of input such as check-boxes, numbers, ranges, strings, and combos.
The first functional call of your plugin needs to be menup(MANIFEST_TABLE)! An example of everything detailed in this document can be found at the end. You can also look at the included plugins for more examples.

๐Ÿ“œ Manifest

A plugin manifest is a table containing general information about the plugin, as well as an optional config table which will be outlined later.

  • id: A unique ID to represent the plugin. Should be in the format of "author.plugin", example: "djsime1.pling"
  • author: Your name. This does NOT have to match what you wrote in the ID.
  • name: The name of the plugin. Once again, this does NOT have to match the ID.
  • description: A description of what the plugin does. This can be authored with Markdown!
  • version: A version string. So long as it contains numbers (can be separated by anything) it will be used to notify the user when an update is available from the source link below.
  • source: A optional link to a file containing the latest version of this plugin.
  • changelog: An optional markdown-formatted changelog. Useless if version and source aren't provided above.
  • api: Set this to the current major release number of MPR. (Eg. if running v. 0.1.6, set this to 0, or if running 1.3.4, set this to 1.)
  • dependencies: A optional table containing any plugin ID's that this plugin depends on. Warns the user if any are missing or disabled. This table can be sequential (list of dependent plugins) or K:V, where the key is the plugin ID, and the value is a URL to where it can be downloaded.
  • config: An OPTIONAL table containing your plugin's configuration. Read more below.

Note: api and dependencies currently exist as specification, and are not checked when loading plugins yet. These will become functional soon.

โš™๏ธ Config

The config contains a list of options displayed alongside your plugin's entry in the manager window. This table is optional, and if not supplied, then the config tab will be disabled for your plugin. Think of this table as a skeleton for your configuration, which declares the name, type, and default for each config option. Details on accessing these options will be defined in a later wiki page.
The format of the config table goes as such: id = {name, type, default/argument, helper-text}

ID

The ID of the option is how you'll get it's value later. Within the list of options, this key is used to visually sort the option alphabetically.

Name

This is the name shown to the user alongside the input. The name should be kept short, as further helper text can be defined that is displayed upon hovering over the option.

Type

The input type of the option, which is one of the following:

  • bool: A simple check-box.
  • int: A integer with no limits. (Decimals are cut off)
  • float: A float with no limits. (Integer with decimals)
  • range: A float constrained between two numbers.
  • string: A string with no length limit.
  • keybind: A DBinder. (Value stores KEY enum as a number)
  • file: A file within a specific base directory. (Value stores relative path, append base for full path)
  • color: A Color with alpha. (Value stored as color)
  • select: A drop-down box containing a set of predefined items. (Only one can be selected)
  • stack: A drop-down box with True/False ticks for each item. (Multiple can be selected)
  • sort: A list where items can be re-arranged, but not modified.
  • list: A list where items can be added, modified, removed, and re-arranged.

Default / Argument

For bool, int, float, string, sort, and list types, this is the default value.
For other types, it is a table containing the following:

  • range: {min, max, default}
  • keybind: number value of KEY Enum (Must be a number, the Enum won't work)
  • file: {base, match, default} (Base: Root directory within GAME, Match: Filter string, same as file.Find, Default: Default file path relative to Base)
  • color: {r, g, b, a} (Same arguments as you would put in Color(...), alpha defaults to 255)
  • select: {"item1", "item2", "item3..."} (An empty string can be used to insert a separator line)
  • stack: {A = boolean, B = boolean, C = boolean} (Values appear alphabetically in config menu)

Helper text

This optional string is displayed whenever the user hovers over the option.

โ†ฉ๏ธ Unload function

If your plugin needs to do revert a change upon being disabled/unloaded, return a function that does so at the end. This is not required, however it should be used to clean up any hooks or replace any overridden functions. An example can be found below.

๐Ÿ’ก Example

local CONFIG = {
    somebool = {"Example bool", "bool", true, "Example description"},
    someint = {"Example int", "int", 10},
    somefloat = {"Example float", "float", 420.69, "nice"},
    somerange = {"Example range", "range", {0, 100, 50}}, -- min max default
    somestr = {"Example string", "string", "Bazinga!"}, -- default is also placeholder
    somebind = {"Example bind", "keybind", 67}, -- numerical value of KEY_TAB
    somefile = {"Example file", "file", {"sound", "*", "garrysmod/content_downloaded.wav"}}, -- allows any file in the sound directory
    somecolor = {"Example color", "color", {255, 255, 0, 128}}, -- 50% transparent yellow
    somesel = {"Example select", "select", {"Apple", "Orange", "Banana","","Socially distanced banana"}}, -- first item is default, empty string for spacer.
    somestack = {"Example stack", "stack", {Apple = true, Orange = false, Banana = true}}, -- sorted as Apple, Banana, Orange in menu.
    somesort = {"Example sort", "sort", {"Apple", "Orange", "Banana"}}, -- values are immutable.
    somelist = {"Example list", "list", {"Apple", "Orange", "Banana"}} -- values can be modified.
}

local MANIFEST = {
    id = "djsime1.example", -- Should (but technically doesn't have to) follow this format.
    author = "djsime1", -- Does not have to match above.
    name = "Manifest & Config example",
    description = "_This is the description of your plugin, **which can be formatted using Markdown**._",
    version = "1.0", -- Numbers can be separated by anything.
    source = "https://example.com/my_cool_plugin.lua", -- A link to the latest version of this plugin.
    changelog = "Added something cool, fixed some bugs, removed the dislike button.", -- Can be formatted with Markdown
    api = 0, -- Read about how to set this above.
    -- Dependencies can be specified in two ways, pick one or the other. Sequential:
    dependencies = {"djsime1.example_library", "somebody.something"}, -- Warns if these plugins aren't installed and enabled.
    -- Alternatively, you can link to the sources of the dependencies. KV:
    dependencies = {
        ["djsime1.example_library"] = "https://github.com/somebody/something/file.lua", 
        ["somebody.something"] = "https://github.com/somebody/something/file.lua",
    }, -- Same as sequential, but also links to the source files.
    config = CONFIG
}

-- The following MUST be the first functional call of your plugin!!

menup(MANIFEST)

-- The rest of your plugin goes after here.
-- If your plugin needs to do revert a change upon being disabled/unloaded, return a function.
-- This is not required, however it should be used to clean up any hooks or replace any overridden functions.

return function()
   hook.Remove("DrawOverlay", "ExampleHookName")
end

๐Ÿ‘‰ Next steps

Now that you know the foundation of a plugin, you may benefit from the features provided by MPR. Here are some links to extra resources: