Completes the uMod mirror beyond the Rust hook table, and adds agent/ — the same facts in TSV/JSONL at ~46% of the tokens. New prose: - OXIDE_API.md: the 19 developer pages under umod.org/documentation/api/ — plugin structure, hooks, commands, IPlayer, permissions, config, data files, database, localization, timers, web requests, dependencies, integration, preprocessor directives, security, style guide, CI, review. This is the framework our plugin is a guest in, where HOOKS.md is what the game says. - OPERATING.md: the 6 operator pages — installing Oxide on a server, then installing, configuring and permissioning plugins. New machine-readable set (agent/): - hooks.tsv 477 rows, ~31% of HOOKS.md - items.tsv 678 rows, ~80% of DEFINITIONS.md's item table - skins.tsv 104 rows covering 2,590 skins, ~77% - api.jsonl 150 code examples, ~47% of the two prose docs Generated in the same pass as the markdown, so the two cannot drift. HOOKS.md gains the universal-hook split: 34 of the 477 are uMod's own Covalence hooks, raised identically on every game uMod supports. Verified against /documentation/games/universal in the same capture - all 34 are in the Rust set and the Rust page adds none of its own, so the overlap is exact. The distinction is architectural: a universal hook is the portable part of the surface. Two things worth recording from building it: - The first skins.tsv was one row per skin and came out 11% LARGER than the markdown it replaces. Grouping it one row per item is what made it a saving. The token win is real for prose (3.2x on hooks) and small for tables that were already dense - agent/README.md says so plainly rather than claiming a flat number. - Signature extraction by brace depth silently captured body lines (a nested '}' in OnUserConnected's example ended the block early). It now matches on the hook's own name; all 477 rows verified to carry a real signature. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016wDDVXWMDz82WqE1i969r4
2073 lines
54 KiB
Markdown
2073 lines
54 KiB
Markdown
# The Oxide/uMod plugin API
|
|
|
|
The **game-independent** half of the ecosystem: how an Oxide plugin is structured, how it
|
|
declares hooks and commands, how it persists state, and how it talks to other plugins. Pair it
|
|
with [`HOOKS.md`](HOOKS.md), which is the Rust-specific half — *what* the game will tell you.
|
|
|
|
> **Mirrored verbatim from uMod's developer documentation on 2026-09-15** — 19 pages under
|
|
> `umod.org/documentation/api/`. uMod is upstream and normative; this copy exists so the work can
|
|
> be done offline and grepped. See [`README.md`](README.md) for provenance and refresh.
|
|
|
|
---
|
|
|
|
## Contents
|
|
|
|
**Orientation**
|
|
|
|
- [Overview](#overview) — What a plugin is, and the two base classes
|
|
- [Getting Started](#getting-started) — The smallest plugin that loads, and the file layout
|
|
|
|
**The core surfaces**
|
|
|
|
- [Hooks](#hooks) — Declaring hooks, calling them, and subscribing/unsubscribing at runtime
|
|
- [Commands](#commands) — Chat and console commands
|
|
- [Player Interface](#player-interface) — The `IPlayer` interface — the game-agnostic player object
|
|
- [Permissions](#permissions) — Registering permissions, groups, ranks
|
|
|
|
**State and I/O**
|
|
|
|
- [Configuration](#configuration) — The config file, defaults, and reading values
|
|
- [Data Files](#data-files) — Persisting plugin state to disk
|
|
- [Database](#database) — SQLite and MySQL access
|
|
- [Localization](#localization) — Message catalogues and per-player language
|
|
- [Timers](#timers) — One-shot, repeating, continuous and next-frame timers
|
|
- [Web Requests](#web-requests) — Outbound HTTP from a plugin
|
|
|
|
**Talking to other plugins**
|
|
|
|
- [Dependencies](#dependencies) — Hard and soft dependencies
|
|
- [Integration](#integration) — Calling another plugin, and exposing your own API
|
|
|
|
**Build and release**
|
|
|
|
- [Preprocessor Directives](#preprocessor-directives) — Per-game compilation symbols
|
|
- [Security](#security) — What a plugin must not do
|
|
- [Style Guide](#style-guide) — uMod's house style
|
|
- [Continuous Integration](#continuous-integration) — Building plugins in CI
|
|
- [Approval Guide](#approval-guide) — What uMod's reviewers check before publishing a plugin
|
|
|
|
---
|
|
|
|
<a id="overview"></a>
|
|
|
|
## Overview
|
|
|
|
<sub>Source: <https://umod.org/documentation/api/overview></sub>
|
|
|
|
---
|
|
|
|
### .NET framework
|
|
|
|
Oxide and all of the games that Oxide actively supports are written in .NET or provide some interface through .NET.
|
|
|
|
This guide is not meant to serve as introduction to programming. Though the entry level for Oxide is low, we do assume some basic programming knowledge.
|
|
|
|
### CSharp ecosystem
|
|
|
|
Learning C# is fundamental to using Oxide. Oxide and all resources that Oxide currently supports are written in C#.
|
|
|
|
Technically Oxide has legacy support for JS, Lua, and Python plugins; however, these are currently unsupported and will likely remain so due to lack of community interest.
|
|
|
|
There are [hundreds](https://umod.org/plugins) of free and open-source examples of working C# code written for Oxide and Oxide supported games.
|
|
|
|
### Server-side modification
|
|
|
|
All of the games that Oxide supports are currently multiplayer games which have both a client-side and a server-side component.
|
|
|
|
A game client is generally an executable file that a player runs locally on their machine to play the game.
|
|
|
|
In contrast, a server is generally a service running on another machine over the internet which a player connects to in order to play the game with other players.
|
|
|
|
Oxide support is injected into the server-side component exclusively. Oxide is **not** a game client modification. Our contributors are largely limited to capabilities (or lack thereof) provided by third-party game developers. This includes...
|
|
|
|
1. Core game mechanics.
|
|
2. Client-server integration
|
|
3. Error handling
|
|
4. Game engine
|
|
|
|
### Plugins and hot loading
|
|
|
|
Oxide is bundled with a C# compiler. Plugins are downloaded and installed as raw source code, and may be unloaded or loaded at a whim.
|
|
|
|
This is is especially useful during development, as Oxide will detect when plugin source code has changed and automatically reload the given plugin.
|
|
|
|
### Extensions and products
|
|
|
|
Most extensions are free and open-source binaries and must be deployed as `.dll` files. Contrary to plugins, extensions may not be hot loaded and require a server restart for every update.
|
|
|
|
Products are generally closed-source proprietary plugins or extensions which are also deployed as `.dll` files or remotely through our upcoming Marketplace extension.
|
|
|
|
---
|
|
|
|
<a id="getting-started"></a>
|
|
|
|
## Getting Started
|
|
|
|
<sub>Source: <https://umod.org/documentation/api/getting-started></sub>
|
|
|
|
---
|
|
|
|
Below is a minimal example of an Oxide plugin.
|
|
|
|
```csharp
|
|
namespace Oxide.Plugins
|
|
{
|
|
[Info("Epic Stuff", "Unknown Author", "0.1.0")]
|
|
[Description("Makes epic stuff happen")]
|
|
class EpicStuff : CovalencePlugin
|
|
{
|
|
private void Init()
|
|
{
|
|
Puts("A baby plugin is born!");
|
|
}
|
|
|
|
// The rest of the code magic
|
|
|
|
// TODO (you): Make more epic stuff
|
|
}
|
|
}
|
|
```
|
|
|
|
### Namespace
|
|
|
|
The namespace of each plugin should be `Oxide.Plugins` and will always be the same due to how plugins are currently setup to interact with each other and the core.
|
|
|
|
```csharp
|
|
namespace Oxide.Plugins
|
|
```
|
|
|
|
### Title
|
|
|
|
The Title variable *(first part of Info attribute)* is what defines the plugin. This should be a unique name or codename related to the purpose of the plugin. This variable (Title) can be accessed throughout the plugin in non-static methods as well.
|
|
|
|
```csharp
|
|
"Epic Stuff"
|
|
```
|
|
|
|
### Author
|
|
|
|
The Author variable *(second part of Info attribute)* is used to show who made or currently maintains the plugin. This should match the author's uMod.org username (if releasing). This (Author) variable can be accessed throughout the plugin in non-static methods as well.
|
|
|
|
```csharp
|
|
"Unknown Author"
|
|
```
|
|
|
|
### Version
|
|
|
|
The Version variable *(third part of Info attribute)* is used to tell if the plugin is outdated or not and should be incremented with each release. [Semantic Versioning](http://semver.org/) is recommended. This variable (Version) can be accessed throughout the plugin in non-static methods as well.
|
|
|
|
```csharp
|
|
"0.1.0"
|
|
```
|
|
|
|
### Description
|
|
|
|
The optional Description variable *(standalone Description attribute)* helps explain to users what the plugin does, in case the title is not enough. Make it good, but not too long! This (Description) variable can be accessed throughout the plugin in non-static methods as well.
|
|
|
|
```csharp
|
|
[Description("Makes epic stuff happen")]
|
|
```
|
|
|
|
### Class Name
|
|
|
|
The class name of the plugin is also the Name variable. This needs to match the filename, otherwise warnings will be issued. The class name should not contain any spaces or numbers, and should always start with a capital letter. This variable (Name) can be accessed throughout the plugin in non-static methods as well.
|
|
|
|
```csharp
|
|
class EpicStuff
|
|
```
|
|
|
|
### Plugin Type
|
|
|
|
Plugins should be inheriting the **CovalencePlugin** type. The CovalencePlugin type is used for both universal and non-universal plugins.
|
|
|
|
```csharp
|
|
class EpicStuff : CovalencePlugin
|
|
```
|
|
|
|
---
|
|
|
|
<a id="hooks"></a>
|
|
|
|
## Hooks
|
|
|
|
<sub>Source: <https://umod.org/documentation/api/hooks></sub>
|
|
|
|
uMod supports *hundreds* of hooks and even more are available through plugins and extensions.
|
|
|
|
Hooks are what make a plugin tick in most cases. A hook event is triggered every time a feature aspect of the game (or injection site) is procedurally passed through. A hook, once triggered, will call all plugin methods that are subscribed to it.
|
|
|
|
More generally a hook is a default server-side game behavior which a plugin tracks, modifies, augments, or cancels.
|
|
|
|
---
|
|
|
|
#### Basic example
|
|
|
|
```csharp
|
|
private bool CanUserLogin(string name, string id, string ip)
|
|
{
|
|
Puts("No one can connect");
|
|
return false; // By returning false, no players may connect to this server
|
|
}
|
|
```
|
|
|
|
### Available hooks
|
|
|
|
Please consult our [Games](https://umod.org/documentation/umod/games) documentation for a list of all available hooks.
|
|
|
|
### Custom hooks
|
|
|
|
Developers may leverage hooks to easily develop [integrations](https://umod.org/documentation/api/integration) between plugins.
|
|
|
|
#### Calling a hook from a plugin reference
|
|
|
|
For information about plugin references, please consult [dependencies](https://umod.org/documentation/umod/api/dependencies#optional-dependencies)
|
|
|
|
```csharp
|
|
bool result = EpicStuff.CallHook<bool>("CanDoSomething");
|
|
if (result)
|
|
{
|
|
Puts("Player can do the thing!");
|
|
}
|
|
```
|
|
|
|
#### Calling a hook globally
|
|
|
|
```csharp
|
|
bool result = Interface.CallHook<bool>("CanDoSomething");
|
|
if (result)
|
|
{
|
|
Puts("Player can do the thing!");
|
|
}
|
|
```
|
|
|
|
#### Hook subscription
|
|
|
|
By default, hooks that are included with uMod will be subscribed to automatically by a plugin when the plugin loads. A developer does not need to do anything special to subscribe to a hook aside from write a hook method that has the correct method name and corresponding parameters.
|
|
|
|
However, developers may exercise direct control over which hooks are used by unsubscribing or subscribing to hooks manually.
|
|
|
|
It is recommended for many (CPU intensive) hooks to unsubscribe from them entirely when they are not in use by any feature of a plugin.
|
|
|
|
##### Unsubscribe from hook
|
|
|
|
This example will unsubscribe `OnUserChat` if `chatFeatureEnabled` is `false`.
|
|
|
|
```csharp
|
|
bool chatFeatureEnabled = false;
|
|
|
|
void Init()
|
|
{
|
|
if (!chatFeatureEnabled)
|
|
{
|
|
Unsubscribe("OnUserChat");
|
|
}
|
|
}
|
|
|
|
object OnUserChat(IPlayer player, string message)
|
|
{
|
|
// Do stuff
|
|
}
|
|
```
|
|
|
|
##### Subscribe to hook
|
|
|
|
After unsubscribing from a hook as in the previous example, the plugin may re-subscribe again to re-enable the plugin's chat behavior.
|
|
|
|
```csharp
|
|
[Command("test")]
|
|
private void TestCommand(IPlayer player, string command, string[] args)
|
|
{
|
|
if (player.IsAdmin)
|
|
{
|
|
Puts("Enabling chat behavior");
|
|
Subscribe("OnUserChat");
|
|
}
|
|
}
|
|
```
|
|
|
|
#### Hook overloading
|
|
|
|
Hooks support method overloading. This means that, if possible, a hook will call the method which has a signature that most closely resembles the arguments provided (when the methods have the same name).
|
|
|
|
```csharp
|
|
int EpicNumber = 42;
|
|
string EpicString = "Whoa";
|
|
|
|
Interface.CallHook("EpicHook", EpicNumber);
|
|
Interface.CallHook("EpicHook", EpicNumber, EpicString);
|
|
```
|
|
|
|
To catch the above hooks:
|
|
|
|
```csharp
|
|
private void EpicHook(int epicNumber)
|
|
{
|
|
Puts($"Received a number '{epicNumber}'");
|
|
}
|
|
|
|
private void EpicHook(int epicNumber, string epicString)
|
|
{
|
|
Puts($"Received a number '{epicNumber}' AND string '{epicString}'");
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
<a id="commands"></a>
|
|
|
|
## Commands
|
|
|
|
<sub>Source: <https://umod.org/documentation/api/commands></sub>
|
|
|
|
Custom commands are easily implemented with minimal boilerplate for both in-game chat interfaces and conventional command-line interfaces.
|
|
|
|
---
|
|
|
|
### Chat commands
|
|
|
|
Chat commands are in-game commands entered via the game client's chat, prefixed by a forward slash (/).
|
|
|
|
```csharp
|
|
[Command("test")]
|
|
private void TestCommand(IPlayer player, string command, string[] args)
|
|
{
|
|
player.Reply("Test successful!");
|
|
}
|
|
```
|
|
|
|
### Console commands
|
|
|
|
Console commands may be executed from the server console and in-game interfaces (where applicable).
|
|
|
|
```csharp
|
|
[Command("epicstuff.test")]
|
|
private void TestCommand(IPlayer player, string command, string[] args)
|
|
{
|
|
player.Reply("Test successful!");
|
|
}
|
|
```
|
|
|
|
### Command permissions
|
|
|
|
Easily restrict command usage to players who have a permission assigned to them.
|
|
|
|
```csharp
|
|
[Command("test"), Permission("epicstuff.use")]
|
|
private void TestCommand(IPlayer player, string command, string[] args)
|
|
{
|
|
player.Reply("Test successful!");
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
<a id="player-interface"></a>
|
|
|
|
## Player Interface
|
|
|
|
<sub>Source: <https://umod.org/documentation/api/player></sub>
|
|
|
|
Oxide provides a standard universal method for accessing player information and performing actions on a player using the `IPlayer` interface.
|
|
|
|
---
|
|
|
|
For examples when to use IPlayer in practice, consider the [Commands](https://umod.org/documentation/umod/api/commands) documentation.
|
|
|
|
### Information
|
|
|
|
#### Name
|
|
|
|
The player's in-game display name, which by default is usually the same as their Steam account alias (but not necessarily).
|
|
|
|
```csharp
|
|
string name = player.Name;
|
|
```
|
|
|
|
#### Id
|
|
|
|
The player's unique identification number, in many cases a 64 bit [Steam ID](https://developer.valvesoftware.com/wiki/SteamID) (but not necessarily).
|
|
|
|
```csharp
|
|
string id = player.Id;
|
|
```
|
|
|
|
#### Address
|
|
|
|
The player's IPv4 or IPv6 IP address.
|
|
|
|
```csharp
|
|
string address = player.Address;
|
|
```
|
|
|
|
#### Ping
|
|
|
|
The player's average network ping
|
|
|
|
```csharp
|
|
int ping = player.Ping;
|
|
```
|
|
|
|
#### Language
|
|
|
|
The player's currently configured language. For more information about `CultureInfo`, please see the official [CultureInfo](https://docs.microsoft.com/en-us/dotnet/api/system.globalization.cultureinfo) documentation.
|
|
|
|
```csharp
|
|
CultureInfo language = player.Language;
|
|
```
|
|
|
|
#### IsConnected
|
|
|
|
Whether a player is currently connected to the server
|
|
|
|
```csharp
|
|
if (player.IsConnected)
|
|
{
|
|
player.Reply("You are connected");
|
|
}
|
|
```
|
|
|
|
#### IsServer
|
|
|
|
Whether a player is the server
|
|
|
|
```csharp
|
|
if (player.IsServer)
|
|
{
|
|
player.Reply("You are the server");
|
|
}
|
|
```
|
|
|
|
### Administration
|
|
|
|
#### IsAdmin
|
|
|
|
Whether a player is an administrator
|
|
|
|
```csharp
|
|
if (player.IsAdmin)
|
|
{
|
|
player.Reply("You are an admin");
|
|
}
|
|
```
|
|
|
|
#### IsBanned
|
|
|
|
Whether a player is banned from the server
|
|
|
|
```csharp
|
|
if (player.IsBanned)
|
|
{
|
|
player.Reply("You are banned");
|
|
}
|
|
```
|
|
|
|
#### BanTimeRemaining
|
|
|
|
The amount of time left before a player is unbanned (if ban is temporary). For more information about TimeSpan, please see the official [TimeSpan](https://docs.microsoft.com/en-us/dotnet/api/system.timespan) documentation.
|
|
|
|
```csharp
|
|
TimeSpan banTimeRemaining = player.BanTimeRemaining;
|
|
```
|
|
|
|
#### Ban
|
|
|
|
Bans a player from the server. For more information about TimeSpan, please see the official [TimeSpan](https://docs.microsoft.com/en-us/dotnet/api/system.timespan) documentation.
|
|
|
|
```csharp
|
|
player.Ban("reason"); // Ban player indefinitely
|
|
player.Ban("reason", new TimeSpan(2, 0, 0)); // Ban player for 2 hours
|
|
```
|
|
|
|
#### Unban
|
|
|
|
Unbans a player, allowing them to connect to the server
|
|
|
|
```csharp
|
|
player.Unban();
|
|
```
|
|
|
|
#### Kick
|
|
|
|
Kicks a player from the server
|
|
|
|
```csharp
|
|
player.Kick("reason");
|
|
```
|
|
|
|
### Character
|
|
|
|
#### Health
|
|
|
|
Retrieve or update a player's health
|
|
|
|
```csharp
|
|
float health = player.Health;
|
|
player.Health = 100f;
|
|
```
|
|
|
|
#### MaxHealth
|
|
|
|
Retrieve or update a player's maximum health
|
|
|
|
```csharp
|
|
float maxHealth = player.MaxHealth;
|
|
player.MaxHealth = 50f;
|
|
```
|
|
|
|
#### Heal
|
|
|
|
Heals a player's health a given amount
|
|
|
|
```csharp
|
|
player.Heal(100f);
|
|
```
|
|
|
|
#### Hurt
|
|
|
|
Hurts a player's health a given amount
|
|
|
|
```csharp
|
|
player.Hurt(50f);
|
|
```
|
|
|
|
#### Kill
|
|
|
|
Kills a player, causing them to die
|
|
|
|
```csharp
|
|
player.Kill();
|
|
```
|
|
|
|
#### Rename
|
|
|
|
Renames a player, changing their in-game name
|
|
|
|
```csharp
|
|
player.Rename("EpicName");
|
|
```
|
|
|
|
### Location
|
|
|
|
#### Teleport
|
|
|
|
Teleports a player to the given world position
|
|
|
|
```csharp
|
|
float x = 1;
|
|
float y = 2;
|
|
float z = 3;
|
|
player.Teleport(x, y, z);
|
|
|
|
GenericPosition position = new GenericPosition(x, y, z);
|
|
player.Teleport(position);
|
|
```
|
|
|
|
#### Position
|
|
|
|
Retrieve a player's in-game character position
|
|
|
|
```csharp
|
|
GenericPosition position = player.Position();
|
|
```
|
|
|
|
### Chat and Commands
|
|
|
|
#### Message
|
|
|
|
Sends the given message and prefix to a player
|
|
|
|
```csharp
|
|
player.Message("hello world");
|
|
```
|
|
|
|
#### Reply
|
|
|
|
Replies to a player with the given message and prefix
|
|
|
|
```csharp
|
|
player.Reply("hello world");
|
|
```
|
|
|
|
#### Command
|
|
|
|
Runs the given console command as a player
|
|
|
|
```csharp
|
|
player.Command("command", arg1, arg2 /* , ... */ );
|
|
```
|
|
|
|
### Permissions
|
|
|
|
#### HasPermission
|
|
|
|
Checks if a player has the given permission
|
|
|
|
```csharp
|
|
if (player.HasPermission("epicstuff.use"))
|
|
{
|
|
player.Reply("You have the epic permission");
|
|
}
|
|
```
|
|
|
|
#### GrantPermission
|
|
|
|
Grants a given permission to a player
|
|
|
|
```csharp
|
|
player.GrantPermission("epicstuff.use");
|
|
```
|
|
|
|
#### RevokePermission
|
|
|
|
Removes a given permission from a player
|
|
|
|
```csharp
|
|
player.RevokePermission("epicstuff.use");
|
|
```
|
|
|
|
#### BelongsToGroup
|
|
|
|
Checks if a player belongs to a given group
|
|
|
|
```csharp
|
|
if (player.BelongsToGroup("admin"))
|
|
{
|
|
player.Reply("You are in the admin group");
|
|
}
|
|
```
|
|
|
|
#### AddToGroup
|
|
|
|
Adds a player to a given group
|
|
|
|
```csharp
|
|
player.AddToGroup("admin");
|
|
```
|
|
|
|
#### RemoveFromGroup
|
|
|
|
Removes a player from a given group
|
|
|
|
```csharp
|
|
player.RemoveFromGroup("admin");
|
|
```
|
|
|
|
---
|
|
|
|
<a id="permissions"></a>
|
|
|
|
## Permissions
|
|
|
|
<sub>Source: <https://umod.org/documentation/api/permissions></sub>
|
|
|
|
Oxide offers a substantial API to control user access with permissions and groups
|
|
|
|
### Basic usage
|
|
|
|
For a primer on how to use permissions as a server owner, please consult the *Using the Oxide permissions system* tutorial.
|
|
|
|
Most plugins can benefit from some permissions. Below is a basic example of how to register a permission and check if a player has that permission assigned to them.
|
|
|
|
```csharp
|
|
namespace Oxide.Plugins
|
|
{
|
|
[Info("Epic Stuff", "Unknown Author", "0.1.0")]
|
|
[Description("Makes epic stuff happen")]
|
|
class EpicStuff : CovalencePlugin
|
|
{
|
|
private void Init()
|
|
{
|
|
permission.RegisterPermission("epicstuff.use", this);
|
|
}
|
|
|
|
private void OnUserConnected(IPlayer player)
|
|
{
|
|
if (player.HasPermission("epicstuff.use"))
|
|
{
|
|
// Player has permission, do special stuff for them
|
|
}
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
## API
|
|
|
|
### Groups
|
|
|
|
#### Get all groups
|
|
|
|
```csharp
|
|
string[] groups = permission.GetGroups();
|
|
```
|
|
|
|
#### Check if group exists
|
|
|
|
```csharp
|
|
bool GroupExists = permission.GroupExists("GroupName");
|
|
```
|
|
|
|
#### Create a group
|
|
|
|
```csharp
|
|
bool GroupCreated = permission.CreateGroup("GroupName", "Group Title", 0);
|
|
```
|
|
|
|
#### Remove a group
|
|
|
|
```csharp
|
|
bool GroupRemoved = permission.RemoveGroup("GroupName");
|
|
```
|
|
|
|
#### Check if group has a permission
|
|
|
|
```csharp
|
|
bool GroupHasPermission = permission.GroupHasPermission("GroupName", "epicstuff.use");
|
|
```
|
|
|
|
#### Grant permission to a group
|
|
|
|
```csharp
|
|
permission.GrantGroupPermission("GroupName", "epicstuff.use", this);
|
|
```
|
|
|
|
#### Revoke permission from a group
|
|
|
|
```csharp
|
|
permission.RevokeGroupPermission("GroupName", "epicstuff.use");
|
|
```
|
|
|
|
#### Get the rank for a group
|
|
|
|
```csharp
|
|
int GroupRank = permission.GetGroupRank("GroupName");
|
|
```
|
|
|
|
#### Get the title for a group
|
|
|
|
```csharp
|
|
string GroupTitle = permission.GetGroupTitle("GroupName");
|
|
```
|
|
|
|
#### Get parent group for a group
|
|
|
|
```csharp
|
|
string GroupParent = permission.GetGroupParent("GroupName");
|
|
```
|
|
|
|
#### Get permissions for a group
|
|
|
|
```csharp
|
|
string[] permissions = permission.GetGroupPermissions("GroupName", false);
|
|
```
|
|
|
|
#### Migrate group
|
|
|
|
```csharp
|
|
permission.MigrateGroup("OldGroupName", "NewGroupName");
|
|
```
|
|
|
|
### Users
|
|
|
|
#### Get permissions granted to player
|
|
|
|
```csharp
|
|
string[] UserPermissions = permission.GetUserPermissions("playerID");
|
|
```
|
|
|
|
#### Check if player has a permission
|
|
|
|
```csharp
|
|
bool UserHasPermission = permission.UserHasPermission("playerID", "epicstuff.use");
|
|
```
|
|
|
|
#### Add player to a group
|
|
|
|
```csharp
|
|
permission.AddUserGroup("playerID", "GroupName");
|
|
```
|
|
|
|
#### Remove player from a group
|
|
|
|
```csharp
|
|
permission.RemoveUserGroup("playerID", "GroupName");
|
|
```
|
|
|
|
#### Check if player is in a group
|
|
|
|
```csharp
|
|
bool UserHasGroup = permission.UserHasGroup("playerID", "GroupName");
|
|
```
|
|
|
|
#### Grant permission to a player
|
|
|
|
```csharp
|
|
permission.GrantUserPermission("playerID", "epicstuff.use", this);
|
|
```
|
|
|
|
#### Revoke permission from a player
|
|
|
|
```csharp
|
|
permission.RevokeUserPermission("playerID", "epicstuff.use");
|
|
```
|
|
|
|
### Server
|
|
|
|
#### Get all registered permissions
|
|
|
|
```csharp
|
|
string[] permissions = permission.GetPermissions();
|
|
```
|
|
|
|
#### Check if a permission exists
|
|
|
|
```csharp
|
|
bool PermissionExists = permission.PermissionExists("epicstuff.use", this);
|
|
```
|
|
|
|
#### Register a permission
|
|
|
|
```csharp
|
|
permission.RegisterPermission("epicstuff.use", this);
|
|
```
|
|
|
|
---
|
|
|
|
<a id="configuration"></a>
|
|
|
|
## Configuration
|
|
|
|
<sub>Source: <https://umod.org/documentation/api/configuration></sub>
|
|
|
|
Since many users can not or do not want to edit the plugin directly to change settings or options, it is best to offer them a configuration file that can easily be edited without worrying about messing up the plugin or it resetting each time the plugin is updated.
|
|
|
|
---
|
|
|
|
### Simple configuration
|
|
|
|
#### Creating
|
|
|
|
There are many methods to create a configuration file. The examples below outline the simplest possible usage.
|
|
|
|
```csharp
|
|
protected override void LoadDefaultConfig()
|
|
{
|
|
LogWarning("Creating a new configuration file");
|
|
Config["ShowJoinMessage"] = true;
|
|
Config["ShowLeaveMessage"] = true;
|
|
Config["JoinMessage"] = "Welcome to this server";
|
|
Config["LeaveMessage"] = "Goodbye";
|
|
}
|
|
```
|
|
|
|
#### Updating
|
|
|
|
Modify and save configuration entries by simply assigning the new values and calling the save function.
|
|
|
|
```csharp
|
|
[Command("test")]
|
|
private void TestCommand(IPlayer player, string command, string[] args)
|
|
{
|
|
Config["ShowJoinMessage"] = !(bool)Config["ShowJoinMessage"];
|
|
SaveConfig();
|
|
}
|
|
```
|
|
|
|
### Advanced configuration
|
|
|
|
For large plugins with more elaborate configurations, it may be helpful to scaffold a formal configuration class.
|
|
|
|
```csharp
|
|
private class PluginConfig
|
|
{
|
|
public bool ShowJoinMessage;
|
|
public bool ShowLeaveMessage;
|
|
public string JoinMessage;
|
|
public string LeaveMessage;
|
|
}
|
|
```
|
|
|
|
#### Updating
|
|
|
|
Write the configuration object directly to a file using this simple one-liner.
|
|
|
|
```csharp
|
|
private void SaveConfig()
|
|
{
|
|
Config.WriteObject(config, true);
|
|
}
|
|
```
|
|
|
|
#### Loading
|
|
|
|
Load the configuration object directly from a file.
|
|
|
|
```csharp
|
|
private PluginConfig config;
|
|
|
|
private void Init()
|
|
{
|
|
config = Config.ReadObject<PluginConfig>();
|
|
}
|
|
|
|
protected override void LoadDefaultConfig()
|
|
{
|
|
Config.WriteObject(GetDefaultConfig(), true);
|
|
}
|
|
|
|
private PluginConfig GetDefaultConfig()
|
|
{
|
|
return new PluginConfig
|
|
{
|
|
ShowJoinMessage = true,
|
|
ShowLeaveMessage = true,
|
|
JoinMessage = "Welcome to this server",
|
|
LeaveMessage = "Goodbye"
|
|
};
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
<a id="data-files"></a>
|
|
|
|
## Data Files
|
|
|
|
<sub>Source: <https://umod.org/documentation/api/data-files></sub>
|
|
|
|
Data files are used to store potentially large amounts of arbitrary data.
|
|
|
|
---
|
|
|
|
### Using a data file
|
|
|
|
The `GetDatafile` method will return a `DynamicConfigFile` object. If the file requested already exists, its data will be loaded into the `DynamicConfigFile`. If the file does not exist, it will be created.
|
|
|
|
#### Creating/saving the file
|
|
|
|
```csharp
|
|
DynamicConfigFile dataFile = Interface.Oxide.DataFileSystem.GetDatafile("MyDataFile");
|
|
|
|
dataFile["EpicString"] = "EpicValue";
|
|
dataFile["EpicNumber"] = 42;
|
|
|
|
dataFile.Save();
|
|
```
|
|
|
|
Doing this will create a file at the location `oxide/data/MyDataFile.json` which will have the following contents:
|
|
|
|
```json
|
|
{
|
|
"EpicString" : "EpicValue",
|
|
"EpicNumber" : 42
|
|
}
|
|
```
|
|
|
|
#### Accessing data by key
|
|
|
|
```csharp
|
|
// Check if the EpicString exists
|
|
if (dataFile["EpicString"] != null)
|
|
{
|
|
Puts(dataFile["EpicString"]); // Outputs: EpicValue
|
|
}
|
|
|
|
// Check if the EpicNumber exists
|
|
if (dataFile["EpicNumber"] != null)
|
|
{
|
|
Puts(dataFile["EpicNumber"]); // Outputs: 42
|
|
}
|
|
```
|
|
|
|
#### Removing a key
|
|
|
|
Remove a particular key, using the previous example.
|
|
|
|
```csharp
|
|
dataFile.Remove("EpicString");
|
|
dataFile.Save();
|
|
```
|
|
|
|
The final JSON output:
|
|
|
|
```json
|
|
{
|
|
"EpicNumber" : 42
|
|
}
|
|
```
|
|
|
|
#### Clearing the entire file
|
|
|
|
```csharp
|
|
dataFile.Clear();
|
|
```
|
|
|
|
### Nested keys
|
|
|
|
A developer may use the `DynamicConfigFile` object to easily read and write nested key-value pairs.
|
|
|
|
#### Write nested key
|
|
|
|
```csharp
|
|
dataFile["EpicCategory", "EpicString"] = "EpicValue";
|
|
```
|
|
|
|
```json
|
|
{
|
|
"EpicCategory" : {
|
|
"EpicString" : "EpicValue"
|
|
}
|
|
}
|
|
```
|
|
|
|
#### Read nested key
|
|
|
|
```csharp
|
|
if (dataFile["EpicCategory", "EpicString"] != null)
|
|
{
|
|
Puts(dataFile["EpicCategory", "EpicString"]); // Outputs: EpicValue
|
|
}
|
|
```
|
|
|
|
### Checking if a file exists
|
|
|
|
```csharp
|
|
if (Interface.Oxide.DataFileSystem.ExistsDatafile("MyDataFile"))
|
|
{
|
|
Puts("MyDataFile exists!");
|
|
}
|
|
else
|
|
{
|
|
Puts("MyDataFile does not exist");
|
|
}
|
|
```
|
|
|
|
### Loading a data object
|
|
|
|
Much like [advanced configurations](https://umod.org/documentation/api/configuration#advanced-configuration), data files may be scaffolded using a class definition.
|
|
|
|
```csharp
|
|
private class StoredData
|
|
{
|
|
public HashSet<PlayerInfo> Players = new HashSet<PlayerInfo>();
|
|
|
|
public StoredData()
|
|
{
|
|
}
|
|
}
|
|
|
|
private class PlayerInfo
|
|
{
|
|
public string Id;
|
|
public string Name;
|
|
|
|
public PlayerInfo()
|
|
{
|
|
}
|
|
|
|
public PlayerInfo(IPlayer player)
|
|
{
|
|
Id = player.Id;
|
|
Name = player.Name;
|
|
}
|
|
}
|
|
|
|
private StoredData storedData;
|
|
|
|
private void Init()
|
|
{
|
|
storedData = Interface.Oxide.DataFileSystem.ReadObject<StoredData>("MyDataFile");
|
|
}
|
|
```
|
|
|
|
### Saving a data object
|
|
|
|
Change data files by simply assigning the new values and writing the object to the file.
|
|
|
|
```csharp
|
|
[Command("test")]
|
|
private void TestCommand(IPlayer player, string command, string[] args)
|
|
{
|
|
PlayerInfo info = new PlayerInfo(player);
|
|
if (storedData.Players.Contains(info))
|
|
{
|
|
player.Reply("Your data has already been added to the file");
|
|
}
|
|
else
|
|
{
|
|
storedData.Players.Add(info);
|
|
player.Reply("Saving your data to the file...");
|
|
Interface.Oxide.DataFileSystem.WriteObject("MyDataFile", storedData);
|
|
}
|
|
}
|
|
```
|
|
|
|
### Advanced data
|
|
|
|
For large plugins that potentially store massive amounts of data, using a single data file may not be advisable. In this case, uMod provides the ability to store many smaller data files in a custom sub-directory. These smaller data files may be loaded on an as-needed basis, thus reducing the impact of a plugin on memory and the filesystem overall.
|
|
|
|
```csharp
|
|
private DataFileSystem dataFile;
|
|
|
|
private void Init()
|
|
{
|
|
dataFile = new DataFileSystem($"{Interface.Oxide.DataDirectory}\\player_info");
|
|
}
|
|
|
|
private PlayerInfo LoadPlayerInfo(string playerId)
|
|
{
|
|
return dataFile.ReadObject<PlayerInfo>($"playerInfo_{playerId}");
|
|
}
|
|
|
|
private void SavePlayerInfo(string playerId, PlayerInfo playerInfo)
|
|
{
|
|
dataFile.WriteObject<PlayerInfo>($"playerInfo_{playerId}", playerInfo);
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
<a id="database"></a>
|
|
|
|
## Database
|
|
|
|
<sub>Source: <https://umod.org/documentation/api/database></sub>
|
|
|
|
The Oxide database extensions implement a generalized database abstraction layer for both MySQL and SQLite.
|
|
|
|
---
|
|
|
|
### Open a connection
|
|
|
|
Create a new connection to a database by providing the database file location or an address (URI and port), a database name, and authentication credentials.
|
|
|
|
```csharp
|
|
Core.MySql.Libraries.MySql sqlLibrary = Interface.Oxide.GetLibrary<Core.MySql.Libraries.MySql>();
|
|
Connection sqlConnection = sqlLibrary.OpenDb("localhost", 3306, "umod", "username", "password", this);
|
|
```
|
|
|
|
### Close the connection
|
|
|
|
Close an existing connection to the database.
|
|
|
|
```csharp
|
|
sqlLibrary.CloseDb(sqlConnection);
|
|
```
|
|
|
|
### Query the database
|
|
|
|
Retrieve data from the database, typically using a SELECT statement.
|
|
|
|
```csharp
|
|
string sqlQuery = "SELECT `id`, `field1`, `field2` FROM example_table";
|
|
Sql selectCommand = Oxide.Core.Database.Sql.Builder.Append(sqlQuery);
|
|
|
|
sqlLibrary.Query(selectCommand, sqlConnection, list =>
|
|
{
|
|
if (list == null)
|
|
{
|
|
return; // Empty result or no records found
|
|
}
|
|
|
|
StringBuilder newString = new StringBuilder();
|
|
newString.AppendLine(" id\tfield1\tfield2");
|
|
|
|
// Iterate through resulting records
|
|
foreach (Dictionary<string, object> entry in list)
|
|
{
|
|
newString.AppendFormat(" {0}\t{1}\t{2}\n", entry["id"], entry["field1"], entry["field2"]);
|
|
}
|
|
|
|
Puts(newString.ToString());
|
|
});
|
|
```
|
|
|
|
### Insert query
|
|
|
|
Insert records into the database using an INSERT statement.
|
|
|
|
```csharp
|
|
string sqlQuery = "INSERT INTO example_table (`field1`, `field2`) VALUES (@0, @1);";
|
|
Sql insertCommand = Oxide.Core.Database.Sql.Builder.Append(sqlQuery, "field1 value", "field2 value");
|
|
|
|
sqlLibrary.Insert(insertCommand, sqlConnection, rowsAffected =>
|
|
{
|
|
if (rowsAffected > 0)
|
|
{
|
|
Puts("New record inserted with ID: {0}", sqlConnection.LastInsertRowId);
|
|
}
|
|
});
|
|
```
|
|
|
|
### Update query
|
|
|
|
Update existing records in the database using an UPDATE statement.
|
|
|
|
```csharp
|
|
int exampleId = 2;
|
|
string sqlQuery = "UPDATE example_table SET `field1` = @0, `field2` = @1 WHERE `id` = @2;";
|
|
Sql updateCommand = Oxide.Core.Database.Sql.Builder.Append(sqlQuery, "field1 value", "field2 value", exampleId);
|
|
|
|
sqlLibrary.Update(updateCommand, sqlConnection, rowsAffected =>
|
|
{
|
|
if (rowsAffected > 0)
|
|
{
|
|
Puts("Record successfully updated!");
|
|
}
|
|
});
|
|
```
|
|
|
|
### Delete query
|
|
|
|
Delete existing records from a database using a DELETE statement.
|
|
|
|
```csharp
|
|
int exampleId = 2;
|
|
string sqlQuery = "DELETE FROM example_table WHERE `id` = @0;";
|
|
Sql deleteCommand = Oxide.Core.Database.Sql.Builder.Append(sqlQuery, exampleId);
|
|
|
|
sqlLibrary.Delete(deleteCommand, sqlConnection, rowsAffected =>
|
|
{
|
|
if (rowsAffected > 0)
|
|
{
|
|
Puts("Record successfully deleted!");
|
|
}
|
|
});
|
|
```
|
|
|
|
### Non-query
|
|
|
|
By definition a non-query is a query which modifies data and does not retrieve data. Insert, Update, and Delete queries are all considered non-queries.
|
|
|
|
```csharp
|
|
int exampleId = 2;
|
|
string sqlQuery = "UPDATE example_table SET `field1` = @0, `field2` = @1 WHERE `id` = @3;";
|
|
Sql sqlCommand = Oxide.Core.Database.Sql.Builder.Append(sqlQuery, "field1 value", "field2 value", exampleId);
|
|
|
|
sqlLibrary.ExecuteNonQuery(sqlCommand, sqlConnection);
|
|
```
|
|
|
|
---
|
|
|
|
<a id="localization"></a>
|
|
|
|
## Localization
|
|
|
|
<sub>Source: <https://umod.org/documentation/api/localization></sub>
|
|
|
|
Oxide provides a simple API with which a developer may easily add support for multiple languages to plugins and extensions.
|
|
|
|
---
|
|
|
|
### Localization guidelines
|
|
|
|
1. English, please
|
|
While we certainly encourage submissions that support multiple languages, English *must* be included.
|
|
2. No prefixes
|
|
While not required, not prefixing messages with your plugin name is generally encouraged as it keeps the messages sent to players shorter and cleaner. Some plugin developers opt for having the prefix configurable via the configuration file, but most do not set one.
|
|
3. Format
|
|
Try to keep messages brief and to the point to avoid taking up an excess amount of screen or console space. Best practice is to use Sentence case, not Title Case or ALL CAPS.
|
|
|
|
### Plugin messages
|
|
|
|
#### Registering messages
|
|
|
|
When a plugin loads, it must register all of the messages used in the plugin.
|
|
|
|
```csharp
|
|
protected override void LoadDefaultMessages()
|
|
{
|
|
lang.RegisterMessages(new Dictionary<string, string>
|
|
{
|
|
["EpicThing"] = "An epic thing has happened",
|
|
["EpicTimes"] = "An epic thing has happened: {0} time(s)"
|
|
}, this);
|
|
}
|
|
```
|
|
|
|
#### Get all messages
|
|
|
|
Retrieves a Dictionary of all the messages registered for a particular plugin and language.
|
|
|
|
```csharp
|
|
Dictionary<string, string> messages = lang.GetMessages("en", this);
|
|
|
|
Puts($"Messages for {Title}:");
|
|
foreach(KeyValuePair<string, string> message in messages)
|
|
{
|
|
Puts($"{message.Key}: {message.Value}");
|
|
}
|
|
```
|
|
|
|
#### Get a single message
|
|
|
|
```csharp
|
|
[Command("epicstuff.message")]
|
|
private void TestMessageCommand(IPlayer player)
|
|
{
|
|
string message = lang.GetMessage("EpicThing", this, player.Id);
|
|
Puts(message);
|
|
}
|
|
```
|
|
|
|
> **Note.**
|
|
> **The player's ID is passed to the `GetMessage` method, which will send them the message in their language when available.**
|
|
|
|
#### Formatting a message
|
|
|
|
```csharp
|
|
int amount = 0;
|
|
|
|
[Command("epicstuff.amount")]
|
|
private void TestAmountCommand(IPlayer player)
|
|
{
|
|
amount++;
|
|
string message = lang.GetMessage("EpicTimes", this, player.Id);
|
|
Puts(string.Format(message, amount.ToString()));
|
|
}
|
|
```
|
|
|
|
### Player language
|
|
|
|
#### Get player language
|
|
|
|
Retrieve the server-wide language setting for a player.
|
|
|
|
```csharp
|
|
[Command("epicstuff.language")]
|
|
private void TestLanguageCommand(IPlayer player)
|
|
{
|
|
Puts(lang.GetLanguage(player.Id));
|
|
// Will output (by default): en
|
|
}
|
|
```
|
|
|
|
#### Set player language
|
|
|
|
Update the server-wide language setting for a player.
|
|
|
|
```csharp
|
|
[Command("epicstuff.french")]
|
|
private void TestUpdateCommand(IPlayer player)
|
|
{
|
|
lang.SetLanguage("fr", player.Id);
|
|
Puts("Merci bien! Votre langue est le français");
|
|
}
|
|
```
|
|
|
|
Players can also set their language by using the included `oxide.lang`, `o.lang`, or `lang` console or chat commands along with their desired, available two-letter language code.
|
|
|
|
### Plugin languages
|
|
|
|
#### Get plugin languages
|
|
|
|
Retrieves an array of strings containing a list of all the languages that a plugin supports.
|
|
|
|
```csharp
|
|
string[] languages = lang.GetLanguages(this);
|
|
|
|
Puts($"Supported languages for {Title}:");
|
|
foreach(string language in languages)
|
|
{
|
|
Puts(language);
|
|
}
|
|
```
|
|
|
|
### Server language
|
|
|
|
#### Get server language
|
|
|
|
Retrieve the default language for the server.
|
|
|
|
```csharp
|
|
Puts(lang.GetServerLanguage()); // Will output (by default): en
|
|
```
|
|
|
|
#### Set server language
|
|
|
|
Update the default language for the server.
|
|
|
|
```csharp
|
|
lang.SetServerLanguage("fr"); // Will set language to "fr"
|
|
```
|
|
|
|
---
|
|
|
|
<a id="timers"></a>
|
|
|
|
## Timers
|
|
|
|
<sub>Source: <https://umod.org/documentation/api/timers></sub>
|
|
|
|
Timers generally execute functions after a set interval. Optionally continuous, repeating, and immediate timers are also available.
|
|
|
|
---
|
|
|
|
### Single timer
|
|
|
|
Executes a function once after the specified delay interval.
|
|
|
|
```csharp
|
|
timer.Once(1f, () =>
|
|
{
|
|
Puts("Hello world!");
|
|
});
|
|
```
|
|
|
|
### Continuous timer
|
|
|
|
Executes a function at the specified delay interval (until the timer is manually destroyed or plugin is unloaded).
|
|
|
|
```csharp
|
|
timer.Every(3f, () =>
|
|
{
|
|
Puts("Hello world!");
|
|
});
|
|
```
|
|
|
|
### Repeating timer
|
|
|
|
Executes a function a specific number of times at the specified delay interval. If the number of recurrences is not specified (0), then a repeating timer behaves identically to a continuous timer.
|
|
|
|
```csharp
|
|
timer.Repeat(5f, 0, () =>
|
|
{
|
|
Puts("Hello world!");
|
|
});
|
|
```
|
|
|
|
### Immediate timer
|
|
|
|
Executes a function immediately (in the next frame).
|
|
|
|
```csharp
|
|
NextFrame(() =>
|
|
{
|
|
Puts("Hello world!");
|
|
});
|
|
```
|
|
|
|
### Destroying timer
|
|
|
|
When a timer is no longer operating, it is marked as destroyed. Additionally timers may be destroyed manually if stored in a variable.
|
|
|
|
```csharp
|
|
Timer myTimer = timer.Every(3f, () =>
|
|
{
|
|
Puts("Hello world!");
|
|
});
|
|
|
|
myTimer.Destroy();
|
|
if (myTimer.Destroyed)
|
|
{
|
|
Puts("Timer destroyed!");
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
<a id="web-requests"></a>
|
|
|
|
## Web Requests
|
|
|
|
<sub>Source: <https://umod.org/documentation/api/web-requests></sub>
|
|
|
|
Make a web request to a URI (Uniform Resource Identifier) using the HTTP GET, POST, or PUT methods.
|
|
|
|
Web requests create a raw connection to a web page as done in a web browser. The request will return true if the web request was sent, false if not.
|
|
|
|
The callback is called with 2 parameters - an integer HTTP response code and a string response.
|
|
|
|
---
|
|
|
|
### GET web request
|
|
|
|
The HTTP GET method is used to retrieve a resource, usually represented as XML or JSON. HTTP status code 200 (OK) is expected in response to a successful GET request.
|
|
|
|
```csharp
|
|
webrequest.Enqueue("http://www.google.com/search?q=umod", null, (code, response) =>
|
|
{
|
|
if (code != 200 || response == null)
|
|
{
|
|
Puts($"Couldn't get an answer from Google!");
|
|
return;
|
|
}
|
|
Puts($"Google answered: {response}");
|
|
}, this, RequestMethod.GET);
|
|
```
|
|
|
|
#### Advanced GET request
|
|
|
|
The following example demonstrates how to specify custom request timeout and/or additional headers.
|
|
|
|
```csharp
|
|
[Command("get")]
|
|
private void GetRequest(IPlayer player, string command, string[] args)
|
|
{
|
|
// Set a custom timeout (in milliseconds)
|
|
float timeout = 200f;
|
|
|
|
// Set some custom request headers (eg. for HTTP Basic Auth)
|
|
Dictionary<string, string> headers = new Dictionary<string, string> { { "header", "value" } };
|
|
|
|
webrequest.Enqueue("http://www.google.com/search?q=umod", null, (code, response) =>
|
|
GetCallback(code, response, player), this, RequestMethod.GET, headers, timeout);
|
|
}
|
|
|
|
private void GetCallback(int code, string response, IPlayer player)
|
|
{
|
|
if (response == null || code != 200)
|
|
{
|
|
Puts($"Error: {code} - Couldn't get an answer from Google for {player.Name}");
|
|
return;
|
|
}
|
|
|
|
Puts($"Google answered for {player.Name}: {response}");
|
|
}
|
|
```
|
|
|
|
### POST web request
|
|
|
|
The HTTP POST method is generally used to create new resources. HTTP status code 200 (OK) OR HTTP status code 201 (Created), and an accompanying redirect (to the newly created resource) are expected in response to a successful POST request.
|
|
|
|
```csharp
|
|
webrequest.Enqueue("http://www.google.com/search?q=umod", "param1=value1", (code, response) =>
|
|
{
|
|
if (code != 200 || response == null)
|
|
{
|
|
Puts($"Couldn't get an answer from Google!");
|
|
return;
|
|
}
|
|
Puts($"Google answered: {response}");
|
|
}, this, RequestMethod.POST);
|
|
```
|
|
|
|
### PUT web request
|
|
|
|
The HTTP PUT is generally used to update existing resources. The request body of a PUT request generally contains an updated representation of the original resource. HTTP status code 200 (OK) OR HTTP status code 204 (No Content) are expected in response to a successful PUT request.
|
|
|
|
```csharp
|
|
webrequest.Enqueue("http://www.google.com/search?q=umod", null, (code, response) =>
|
|
{
|
|
if (code != 200 || response == null)
|
|
{
|
|
Puts($"Couldn't get an answer from Google!");
|
|
return;
|
|
}
|
|
Puts($"Google answered: {response}");
|
|
}, this, RequestMethod.PUT);
|
|
```
|
|
|
|
### POST and PUT body
|
|
|
|
Typically an updated resource is represented in a POST/PUT request body as a query string.
|
|
|
|
```csharp
|
|
Dictionary<string,string> parameters = new Dictionary<string,string>();
|
|
|
|
parameters.Add("param1", "value1");
|
|
parameters.Add("param2", "value2");
|
|
|
|
string[] body = string.Join("&", parameters.Cast<string>().Select(key => string.Format("{0}={1}", key, source[key]));
|
|
webrequest.Enqueue("http://www.google.com/search?q=umod", body, (code, response) =>
|
|
{
|
|
if (code != 200 || response == null)
|
|
{
|
|
Puts($"Couldn't get an answer from Google!");
|
|
return;
|
|
}
|
|
Puts($"Google answered: {response}");
|
|
}, this, RequestMethod.POST);
|
|
```
|
|
|
|
### Using a method callback
|
|
|
|
The following example demonstrates how to refactor delegate behavior by encapsulating it in a separate method, rather than solely using an anonymous function.
|
|
|
|
```csharp
|
|
[Command("get")]
|
|
private void GetRequest(IPlayer player, string command, string[] args)
|
|
{
|
|
webrequest.EnqueueGet("http://www.google.com/search?q=umod", (code, response) => GetCallback(code, response, player), this);
|
|
}
|
|
|
|
private void GetCallback(int code, string response, IPlayer player)
|
|
{
|
|
if (response == null || code != 200)
|
|
{
|
|
Puts($"Error: {code} - Couldn't get an answer from Google for {player.Name}");
|
|
return;
|
|
}
|
|
|
|
Puts($"Google answered for {player.Name}: {response}");
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
<a id="dependencies"></a>
|
|
|
|
## Dependencies
|
|
|
|
<sub>Source: <https://umod.org/documentation/api/dependencies></sub>
|
|
|
|
Dependencies are functional relationships between different code resources.
|
|
|
|
---
|
|
|
|
### Optional dependencies
|
|
|
|
Optional dependencies are plugins, extensions, products, or third-party libraries that your code *can* use but does not require to work.
|
|
|
|
Generally an optional dependency means checking for the installation of another resource (i.e. plugin), and disabling or enabling features depending on its existence.
|
|
|
|
#### Basic plugin reference
|
|
|
|
A plugin reference field allows developers to magically reference another plugin. If the plugin is not available, the reference field will return `null`.
|
|
|
|
The name of the plugin reference field must match the class name of the plugin being referenced.
|
|
|
|
```csharp
|
|
[PluginReference]
|
|
private Plugin EpicStuff;
|
|
|
|
private void Loaded()
|
|
{
|
|
if (EpicStuff != null)
|
|
{
|
|
EpicStuff.Call("SomeMethod");
|
|
}
|
|
}
|
|
```
|
|
|
|
### Required dependencies
|
|
|
|
Resource requirements are plugins, extensions, products, or third-party libraries that a plugin *must* have in order to work.
|
|
|
|
Requirements are *not* optional, and the code must check for them and display a helpful error message when they are unavailable.
|
|
|
|
```csharp
|
|
private void Loaded()
|
|
{
|
|
if (EpicStuff == null)
|
|
{
|
|
LogError("Epic Stuff is not loaded, get it at https://umod.org");
|
|
}
|
|
}
|
|
```
|
|
|
|
### Hard dependencies
|
|
|
|
"Hard" dependencies are special cases where a resource (i.e. plugin) is compiled alongside another plugin and may be referenced directly (as opposed to indirectly through Oxide).
|
|
|
|
It is *not* recommended to use hard dependencies except when it is absolutely needed.
|
|
|
|
> **Warning.**
|
|
> **If a hard dependency is not installed alongside a plugin that requires it, a compilation error will be thrown.**
|
|
|
|
At the top of the plugin file, above using statements:
|
|
|
|
```csharp
|
|
// Requires: EpicStuff
|
|
```
|
|
|
|
In the plugin body:
|
|
|
|
```csharp
|
|
// EpicStuff may now be referenced directly
|
|
EpicStuff EpicStuff;
|
|
|
|
private void Loaded()
|
|
{
|
|
EpicStuff = (EpicStuff)Manager.GetPlugin("EpicStuff");
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
<a id="integration"></a>
|
|
|
|
## Integration
|
|
|
|
<sub>Source: <https://umod.org/documentation/api/integration></sub>
|
|
|
|
Oxide provides an interface or intermediate layer to bring together two separate plugins which then cooperate ensuring that both plugins function together as a single system.
|
|
|
|
Providing integrations with other plugins is one of the single best value adding propositions in the Oxide ecosystem.
|
|
|
|
### Modularity and interoperability
|
|
|
|
It is recommended that plugins follow [SOLID](https://en.wikipedia.org/wiki/SOLID_(object-oriented_design)) object oriented design principles. Using these principles correctly often means that disparate functionality is refactored down to constituent parts, each limited to their specific scope and responsibility.
|
|
|
|
This approach is beneficial because, not only does it prevent code from becoming spaghetti (or a big mess), it also ensures each code unit is separated into easily digestible parts for other programmers to understand.
|
|
|
|
### Plugin dependencies
|
|
|
|
Creating a dependency is usually the first step to creating a plugin integration.
|
|
|
|
As outlined in the [dependencies](https://umod.org/documentation/api/dependencies) documentation, there are three (3) different types of dependencies: `optional`, `required`, and `hard`.
|
|
|
|
A basic plugin reference:
|
|
|
|
```csharp
|
|
[PluginReference]
|
|
private Plugin EpicStuff;
|
|
```
|
|
|
|
> **Warning.**
|
|
> **The name of the property (e.g. "EpicStuff") must match the class name of the plugin being referenced exactly.**
|
|
|
|
### Call method
|
|
|
|
After creating a dependency, a plugin may call specific methods in another plugin.
|
|
|
|
```csharp
|
|
private void Loaded()
|
|
{
|
|
if (EpicStuff != null) // check if plugin is loaded
|
|
{
|
|
EpicStuff.Call("SomeMethod", "argument1", "argument2");
|
|
}
|
|
}
|
|
```
|
|
|
|
When return behavior is required the call method returns an object by default. The call method has an optional generic method which may be used to explicitly cast the result.
|
|
|
|
```csharp
|
|
bool someResponse = EpicStuff.Call<bool>("SomeMethod", "argument1", "argument2");
|
|
if(someResponse)
|
|
{
|
|
Puts("SomeMethod Response: True");
|
|
}
|
|
```
|
|
|
|
In order for the above implementations to work, the `EpicStuff` plugin must have a method that matches the signatures used above.
|
|
|
|
```csharp
|
|
// In EpicStuff.cs
|
|
private bool SomeMethod(string argument1, string argument2)
|
|
{
|
|
Puts($"Do stuff: {argument1} {argument2}");
|
|
return true;
|
|
}
|
|
```
|
|
|
|
### Hook conflicts
|
|
|
|
Hooks that have return behavior may conflict when multiple plugins using a hook return different values. In such cases, it is often necessary for one plugin or the other to integrate and resolve the conflict.
|
|
|
|
Hook conflicts usually print a message like...
|
|
|
|
> **Warning.**
|
|
> ****[Warning] Calling hook CanUserLogin resulted in a conflict between the following plugins: MyPlugin - True (Boolean), EpicPlugin (False (Boolean))****
|
|
|
|
The solution is usually to integrate MyPlugin with EpicPlugin (or vice versa) to give one plugin's hook precedence over the other.
|
|
|
|
```csharp
|
|
bool CanUserLogin (string name, string id, string ip)
|
|
{
|
|
if (EpicPlugin != null)
|
|
{
|
|
var result = EpicPlugin.Call ("CanUserLogin", name, id, ip);
|
|
if (result is bool)
|
|
{
|
|
return (bool)result;
|
|
}
|
|
}
|
|
|
|
Puts("No conflict, do plugin stuff here");
|
|
|
|
return true;
|
|
}
|
|
```
|
|
|
|
### Custom hooks
|
|
|
|
Integrations do not necessarily require dependencies, sometimes simply using a [custom hook](https://umod.org/documentation/api/hooks#custom-hooks) is sufficient.
|
|
|
|
For example, if a plugin creates a backpack for players to store their items, it could create a custom hook called `CanCreateBackpack`. Other plugins could then implement the `CanCreateBackpack` hook, and prevent the player from using their backpack in certain situations (e.g. being in an arena).
|
|
|
|
This might be part of a backpack plugin:
|
|
|
|
```csharp
|
|
bool result = Interface.Oxide.CallHook<bool>("CanCreateBackpack", player);
|
|
if (!result)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Create backpack
|
|
```
|
|
|
|
Then in the integration plugin:
|
|
|
|
```csharp
|
|
private object CanCreateBackpack(IPlayer player)
|
|
{
|
|
if (IsInArena(player))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
<a id="preprocessor-directives"></a>
|
|
|
|
## Preprocessor Directives
|
|
|
|
<sub>Source: <https://umod.org/documentation/api/preprocessor-directives></sub>
|
|
|
|
When writing or debugging plugins, it is useful to be familiar with [preprocessor directives](https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/preprocessor-directives/).
|
|
|
|
---
|
|
|
|
### Basic usage
|
|
|
|
Each game and branch that Oxide supports has an accompanying preprocessor symbol.
|
|
|
|
In universal plugins, this symbol may be used to separate code for different games. This is used frequently to ensure that blocks of code are only compiled for a particular game.
|
|
|
|
```csharp
|
|
private bool CanUserLogin(string name, string id, string ip)
|
|
{
|
|
#if RUST
|
|
// Do Rust-specific code
|
|
#elif HURTWORLD
|
|
// Do Hurtworld-specific code
|
|
#endif
|
|
}
|
|
```
|
|
|
|
When a plugin is compiled for Rust, only the first code will be compiled. Conversely, in a Hurtworld context, only the second code will be used.
|
|
|
|
### Available symbols
|
|
|
|
| Game | Symbol |
|
|
|---|---|
|
|
| Rust | `RUST` |
|
|
| Hurtworld | `HURTWORLD` |
|
|
| 7 Days To Die | `SEVENDAYS` |
|
|
| 7 Days To Die latest_experimental | `SEVENDAYS` `SEVENDAYSLATEST_EXPERIMENTAL` |
|
|
| Reign Of Kings | `REIGNOFKINGS` |
|
|
| The Forest | `THEFOREST` |
|
|
|
|
### Custom symbols
|
|
|
|
In some plugins you may see the `DEBUG` symbol or other symbols used. These are primarily for testing purposes.
|
|
|
|
#### Example
|
|
|
|
```csharp
|
|
#if DEBUG
|
|
Puts("Some test related info");
|
|
#endif
|
|
```
|
|
|
|
In order to enable these debugging code blocks, a preprocessor directive must be defined at the beginning of the plugin file.
|
|
|
|
```csharp
|
|
#define DEBUG
|
|
|
|
using System;
|
|
```
|
|
|
|
---
|
|
|
|
<a id="security"></a>
|
|
|
|
## Security
|
|
|
|
<sub>Source: <https://umod.org/documentation/api/security></sub>
|
|
|
|
---
|
|
|
|
### Sandbox
|
|
|
|
Plugins and products are compiled in a restricted mode that prevents a substantial number of .NET features from being used.
|
|
|
|
These restrictions are in place to prevent potentially malicious code.
|
|
|
|
#### Restricted namespaces
|
|
|
|
The list of namespaces restricted by the sandbox includes, but is not limited to:
|
|
|
|
1. `System.IO`
|
|
2. `System.Net`
|
|
3. `System.Reflection`
|
|
4. `System.Threading`
|
|
5. `System.Runtime.InteropServices`
|
|
6. `System.Diagnostics`
|
|
7. `System.Security`
|
|
8. `System.Timers`
|
|
|
|
#### Restriction exceptions
|
|
|
|
There are some exceptions to the above list, and they are:
|
|
|
|
1. `System.Diagnostics.Stopwatch`
|
|
2. `System.IO.MemoryStream`
|
|
3. `System.IO.Stream`
|
|
4. `System.IO.BinaryReader`
|
|
5. `System.IO.BinaryWriter`
|
|
6. `System.Net.Dns`
|
|
7. `System.Net.Dns.GetHostEntry`
|
|
8. `System.Net.IPAddress`
|
|
9. `System.Net.IPEndPoint`
|
|
10. `System.Net.NetworkInformation`
|
|
11. `System.Net.Sockets.SocketFlags`
|
|
12. `System.Security.Cryptography`
|
|
13. `System.Threading.Interlocked`
|
|
|
|
### Extensions
|
|
|
|
Extensions are generally not sandboxed, meaning that any code deployed as an extension will have unmitigated access to all .NET libraries.
|
|
|
|
For this reason, we do not generally accept extensions except in the most needful cases. If code is submitted as an extension, the author ought to be prepared to demonstrate why it *absolutely must* be deployed as an extension.
|
|
|
|
---
|
|
|
|
<a id="style-guide"></a>
|
|
|
|
## Style Guide
|
|
|
|
<sub>Source: <https://umod.org/documentation/api/style-guide></sub>
|
|
|
|
Plugins and extensions require some core scaffolding to be functional and/or approved within the uMod ecosystem.
|
|
|
|
---
|
|
|
|
### Title
|
|
|
|
The title of the plugin (first part of Info attribute in C# plugins) must be set and closely match the submission name on our site. Please avoid using "Plugin" or "uMod" in the title as that would be a bit redundant. Good Ban System
|
|
Bad BanPlugin for Admin
|
|
|
|
### Author
|
|
|
|
The author of the plugin (second part of Info attribute in C# plugins) must be set and match or contain the name of the user submitting the plugin on our site. Please do not use this to advertise websites or game servers, or anything really. Good
|
|
|
|
##### Wulf
|
|
|
|
Bad
|
|
|
|
##### Fluw @ MyServer.net
|
|
|
|
### Version
|
|
|
|
The version of the plugin (third part of Info attribute in C# plugins) must be set in the x.x or "x.x.x" format. This should be updated each time a plugin update is released. Semantic Versioning is recommended, though not fully supported. Good 1.2.3
|
|
Bad 2017-02-01 Beta 1
|
|
|
|
### Description
|
|
|
|
The Description is an optional, standalone attribute, but recommended as other plugins can utilize it for making help commands and such. Please make sure to actually describe the plugin, but keep it brief. Best practice is to use Sentence case, not Title Case or CAPS. Keep it clean! Good Allows admin to ban players easily on command
|
|
Bad Cool Admin BanPlugin
|
|
|
|
---
|
|
|
|
<a id="continuous-integration"></a>
|
|
|
|
## Continuous Integration
|
|
|
|
<sub>Source: <https://umod.org/documentation/api/continuous-integration></sub>
|
|
|
|
For experienced developers, uMod seamlessly integrates both ways with GitHub and GitLab ^(Coming Soon).
|
|
|
|
---
|
|
|
|
### GitHub app
|
|
|
|
By installing the [GitHub app](https://github.com/apps/umod-org) on a repository, the entire plugin development life-cycle may be managed solely through GitHub.
|
|
|
|
#### Documentation
|
|
|
|
Adding or updating a [README.md](https://help.github.com/articles/about-readmes) file in a plugin repository will automatically update the documentation displayed for that plugin on uMod.org
|
|
|
|
#### Licensing
|
|
|
|
Adding or updating a [LICENSE.md](https://help.github.com/articles/adding-a-license-to-a-repository) file in a plugin repository will automatically update the license displayed for that plugin on uMod.org
|
|
|
|
#### Releases
|
|
|
|
Adding a [release](https://help.github.com/articles/creating-releases) of your plugin on GitHub will automatically push a public update of your plugin on uMod.org.
|
|
|
|
#### Branches
|
|
|
|
*Coming Soon!*
|
|
|
|
#### Issues
|
|
|
|
*Coming Soon!*
|
|
|
|
### GitLab app
|
|
|
|
*Coming Soon!*
|
|
|
|
### Builds
|
|
|
|
Our CI server will compile and inspect the plugin automatically against (nearly) all games that the plugin supports upon release.
|
|
|
|
If a build fails, the author will be notified and the release may be hidden from view.
|
|
|
|
---
|
|
|
|
<a id="approval-guide"></a>
|
|
|
|
## Approval Guide
|
|
|
|
<sub>Source: <https://umod.org/documentation/api/approval-guide></sub>
|
|
|
|
In order to ensure a plugin submission is approved, please consult the following guidelines carefully.
|
|
|
|
---
|
|
|
|
### Style
|
|
|
|
Most importantly, the [Style Guide](https://umod.org/documentation/umod/api/style-guide) covers the bare minimum conventions we expect when approving a plugin.
|
|
|
|
### Best practices
|
|
|
|
#### Code quality
|
|
|
|
1. While not a requirement, cleanly formatted code is always appreciated as it makes our job easier when we can easily follow the code of plugin being submitted. [Visual Studio 2015](https://www.visualstudio.com/downloads) or above is always recommended to enable support for the latest C# version as well as numerous options for helping improve your code.
|
|
2. Using statements (i.e. `using System;` at the top of the plugin) can easily get out of hand. Try to only add what is needed by the plugin and remove those that are not. Most development environments such as Visual Studio have options and addons to handle this and more.
|
|
3. Use [static code analysis](https://docs.microsoft.com/en-us/visualstudio/code-quality/code-analysis-for-managed-code-overview?view=vs-2017) tools. After a plugin is submitted or updated, an automatic inspection checks for hundreds of potential issues and scores each discovered issue by severity. Not all issues require developer attention, but most do. Many IDE's are often bundled with static code analysis tools and offer the same or similar functionality.
|
|
4. Do not assume anything passed to a plugin by the game, engine, or hook parameters is not null. Use [null conditional operators](https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/operators/null-conditional-operators) generously.
|
|
|
|
#### Performance
|
|
|
|
1. Each hook call takes up resources and time. Some hooks are called more than others, and each may have a different performance impact depending on game mechanics or server configuration.
|
|
2. Be mindful of how often hooks are called and, when possible, avoid implementing computationally expensive methods in frequently called hooks. Delay or distribute computation cycles, use async or cooperative multitasking, and store frequently used resources in memory.
|
|
3. Unsubscribe from unused hooks. If a plugin only requires a specific hook in certain cases (i.e. when a particular feature is enabled), then disable that hook in all other cases.
|
|
|
|
#### Uniqueness
|
|
|
|
When submitting resources to be published on uMod.org, developers must submit their own work or include express authorization from the original author(s) of the work.
|
|
|
|
While we discourage and do not accept direct copying (or forks) of code from other developers, we generally do *not* deny plugins simply on the basis that they share any of the following components:
|
|
|
|
1. Similar option
|
|
2. Similar use-case
|
|
3. Application design pattern
|
|
4. Integration point
|
|
5. Algorithm
|
|
|
|
### Undesirables
|
|
|
|
There are certain types of plugins that we generally do not approve. These are often because of their general use for abuse, trolling, backdoors, causing conflicts, drama, potentially problematic, or going way beyond their purpose.
|
|
|
|
1. Piracy enabling or authentication bypassing plugins (this should be an obvious one)
|
|
2. Any plugin attempt to bypass sandboxing or security measures put in place (security is important)
|
|
3. Any plugin containing a backdoor or access privileges for specific individuals, even developers
|
|
4. FPS "booster" type plugins (these have been known to be malicious and problematic)
|
|
5. All-in-one does-everything plugins (these defeat the purpose of being modular, often cause conflicts)
|
|
6. Copies of existing plugins with various "fixes" in them (fixes should be contributed to the original)
|
|
7. Copies of existing plugins with messages translated directly (translations can be contributed to the original)
|
|
8. Plugins using any sort of obfuscation method
|
|
|
|
---
|
|
|
|
<sub>Mirrored from uMod developer documentation, 2026-09-15.</sub>
|