Skip to content

4. How To Make a Module

Corey D edited this page Oct 27, 2022 · 5 revisions

Quick Example

In case you don't want to read the guide, here's a quick example:

#include <sourcemod>
#include <ultra_vip>

public void OnPluginStart()
{
    PrintToServer("Module Started");
}

public void UVIP_OnStart()
{
    UVIP_SetupModule();

    if (!UVIP_OverrideFeature(Feature_PlayerHP, Override_Optional))
        SetFailState("Feature_PlayerHP is already being used.");

    char error[256];
    if (!UVIP_RegisterSetting("My_Setting", "100", Type_Integer, Setting_Optional, error, sizeof(error)))
        SetFailState("Failed to register My_Setting. Error: %s", error);

    PrintToServer("Module loaded successfully");
}

public void UVIP_OnSpawnWithService(int client, UVIPService service)
{
    int value = service.GetInt("My_Setting");
    if (value > 0)
        SetEntityHealth(client, value);
}

How To Make an Ultra-VIP module

This guide will explain how to add new bonuses/functionality to Ultra-VIP.
A basic understanding of both SourcePawn and SourceMod in general is required to understand this.

Foreword (or "Can't you just add X so I can Y?")

All other VIP plugins have terrible APIs because almost all SourcePawn developers (myself included) are bad programmers and even worse designers.

You may want to do something using this API that by design shouldn't be allowed just because that's the way other plugins do it.

One example of this is something like IsClientVIP() or having natives to give out VIP access to clients like GiveClientVIP().
Both of these examples are objectively bad design with the way Ultra-VIP works, which is a plugin that both:

  • Has a dynamic/unlimited number of services (not just "VIP").
  • Does not store or manage players admin permissions in any way--it simply gives bonuses to those that already have access.

IsClientVIP() assumes that a "VIP" service exists (it doesn't have to, the config could have anything you want in it. You could make a service just to give to players you hate that makes the game harder for them).
And the existence of GiveClientVIP() essentially makes Ultra-VIP into the worlds worst admin-management plugin--which it is not.

The point is, theres a reason it works the way it does.

What is a module?

Don't be confused by the term, modules are just regular SourceMod plugins that use the include provided by Ultra-VIP.
We call them modules because it's short and easy, but there's nothing special to it.

The Ultra-VIP API has 3 main purposes:

  1. Allow other plugins to create new settings for each Ultra-VIP service that can be configured in a shared config file convenience.
  2. Allow other plugins to disable core features of Ultra-VIP to replace them with their own.
  3. Provide a way to get the Ultra-VIP service that a client has (if any).

What does a module do?

In short, it does the following steps:

  1. (Optional) Override/Disable a core Ultra-VIP feature (only when replacing it).
  2. Register a setting.
  3. Get a clients service (if any).
  4. Get the value of the setting from the clients service (configured by the server operator).
  5. Do something with the value.

"Setting" is sort of similar to SourceMod's own cookie/clientprefs system--it is a named value that is stored per-service rather than per-client.

This allows the setting to be configured differently for each service.
For example we might register the setting "color" and the "VIP" service gets the value "blue" whereas the "Admin" service gets "red".

Anybody with the service will share the same value because it is per-service, not per-client.

Step-By-Step Minimal Example

For this example we will implement our own version of the "player_hp" bonus included with Ultra-VIP.

Include the Ultra-VIP include.

#include <sourcemod>
#include <ultra_vip>

public void OnPluginStart()
{
    // Can't use Ultra-VIP natives yet!
    PrintToServer("Module Started");
}

Setup the module.

In the UVIP_OnStart forward you must always call UVIP_SetupModule() before any other Ultra-VIP natives are used.
The UVIP_OnStart forward is required in every module.

public void UVIP_OnStart()
{
    UVIP_SetupModule();

    // Safe to use Ultra-VIP natives here

    PrintToServer("Module loaded successfully");
}

This function makes sure the installed version of Ultra-VIP is actually compatible with the module, and prevents late-loading which is currently not supported for modules.

(Optional) Override/disable a core Ultra-VIP feature.

To implement our own version of the "player_hp" bonus, we would need to disable the existing feature first so we don't have any conflicts.

Add the following into the UVIP_OnStart() forward.

if (!UVIP_OverrideFeature(Feature_PlayerHP, Override_Optional))
    SetFailState("Feature_PlayerHP is already being used.");

Feature_PlayerHP is from the UVIPFeature enum in the include file.
This enum contains the list of each core Ultra-VIP feature which can be overridden/disabled.

Override_Optional is from the FeatureOverrideMode enum.
It determines what happens to the overriden core feature if the module plugin is unloaded (i.e. should it be re-enabled or stay disabled until the module is reloaded).

By default you should use Override_Optional instead of Override_Required unless you have a reason to.

Calling SetFailState() if UVIP_OverrideFeature returns false is recommended if your module is not going to do anything else, so that it only loads when it is able to work.

Register a setting.

Next, we will create our module's only configurable setting.
Add this to UVIP_OnStart.

char error[256];
if (!UVIP_RegisterSetting("My_Setting", "100", Type_Integer, Setting_Optional, error, sizeof(error)))
    SetFailState("Failed to register My_Setting. Error: %s", error);

"My_Setting" is the name of the setting we are creating. The name is case-insensitive.

Type_Integer is the data type of the setting (SettingType enum).
In this case we are telling Ultra-VIP that any value in the config file that is not an integer is invalid and will cause an error--forcing server operators to use the correct values instead of just trusting them.

The Setting_Optional parameter tells Ultra-VIP whether or not this setting must be set for every service, or if it can be left out and use a default value instead.

"100" is the default value.
Since in this example we are using Setting_Optional and we are replacing the "player_hp" core setting, any service that doesn't have "My_Setting" configured will use 100 instead.

Why SetFailState()?

SetFailState() is optional here, but recommended.

A seting will fail to register if:

  • UVIP_RegisterSetting() is called outside of the UVIP_OnStart forward.
  • The setting name is already used by a different plugin.
  • The setting name or default value are too long (or start with an _)
  • The default value doesn't match the data type parameter (e.g. 3.14 is not a Type_Integer).

However, if this function were to throw an error, you could not stop the plugin and it would error again later trying to use the invalid setting (or even worse, use the value belonging to another plugin!).

Using SetFailState() keeps things simple by stopping the plugin, preventing error log spam.

Setting Configuration

The server operator will need to configure the setting inside of addons/sourcemod/configs/ultra_vip_modules.cfg.
See the module configuration guide for a full explanation on how that works.

Use the setting!

The hard part is done. Now we can just use the value directly by using the UVIPService methodmap.
This methodmap represents the Ultra-VIP service owned by a player.

Note: You cannot use the UVIPService methodmap until after UVIP_OnReady() has been called!

There are 2 ways to get the handle for this methodmap:

  1. The forwards provided by Ultra-VIP.
  2. UVIP_GetClientService(), Which returns null if a client has no service.

Note: When using UVIP_GetClientService(), be aware a client will not have their service (if any) until after OnClientPostAdminCheck().

In this example we'll use the UVIP_OnSpawn forward, which provides us with the client's UVIPService automatically (if they don't have one, it will be null).

public void UVIP_OnSpawn(int client, UVIPService service)
{
    if (service == null)
        return;

    int value = service.GetInt("My_Setting");
    if (value > 0)
        SetEntityHealth(client, value);
}

Or, we could simplify it further using UVIP_OnSpawnWithService, which guarantees the service parameter is not null:

public void UVIP_OnSpawnWithService(int client, UVIPService service)
{
    int value = service.GetInt("My_Setting");
    if (value > 0)
        SetEntityHealth(client, value);
}

That's it. Really!
All the hard work of handling the data for each service is done automatically by Ultra-VIP.


FAQ

"How do I enable a feature for only one service?"

A question I've already been asked before this plugin even released is how to enable a feature for only one service.

This is actually really easy, but you have to think a little differently to other VIP APIs.
You don't enable it for one service.
You create an "is enabled" setting for each service and let the server operators decide which ones have it enabled.

public void UVIP_OnStart()
{
    UVIP_SetupModule();

    char error[256];
    if (!UVIP_RegisterSetting("CoolFeature_Enabled", "1", Type_Bool, Setting_Optional, error, sizeof(error)))
        SetFailState("Failed to register CoolFeature_Enabled. Error: %s", error);
}
public void UVIP_OnSpawn(int client, UVIPService service)
{
    if (service != null && service.GetCell("CoolFeature_Enabled"))
        EnableCoolFeature(client);
    else
        DisableCoolFeature(client);
}

We're using UVIP_OnSpawn here, so we must check if service is null.

GetCell() can be used for float, int, bool or char setting values, but GetInt() or GetFloat() are always preferred for int and float respectively.

"What values can I use for X?"

Read the include file.

"How do I find a service by name?"

You don't. Supporting this would allow modules to hardcode the names which would force server operators to use those specific names.

"Why do I have to use UVIP_RegisterSetting() or UVIP_OverrideFeature() during UVIP_OnStart()?"

Because the settings config needs to be processed after modules set themselves up.
Forcing you to use it during the correct forward ensures the module behaves correctly.