Skip to content
This repository has been archived by the owner on Feb 21, 2023. It is now read-only.
4aiur edited this page Mar 23, 2019 · 3 revisions

What is it

Piping is a system provided by ServerMod to interact with other plugins without requiring dependencies. This allows for smoother integration by resolving plugin conflictions, as well as extended functionality by hooking to plugin events, calling your own events, or calling a specific plugin's methods.

How do they work?

All but events are calculated and set after the plugin's Register() method. From there, all a plugin has to do is use it like a normal variable, property, or method with a small amount of wrapping.

Events are currently calculated during run-time and exceptions thrown by event hooks will be caught and reported. Be careful about this, as this means that passing invalid parameters can cause exceptions which look like other plugins' faults.

Features

All exports

  • do not have to be public.

All imports

  • have types that are backwards names of the exports (e.g. PipeField export: swapping Pipe and Field makes FieldPipe import).
  • have a generic type for convenience purposes. These generics also support implicit casting, so in some cases the generics can be used just like their original type.
  • do not care about if the field is readonly (the field that the PipeLink marks).

Good practices include:

  • keeping same name as the pipe export.
  • using objects that do not require any additional dependencies (the whole point of pipes is to make plugins less dependent but keep integration support).

PipeField

Allows for other plugins to get/set a field marked by this attribute. Settability is determined by whether or not the field is readonly, however it can be overridden in the constructor.

Export Example

// Gettable and settable fields.
[PipeField]
public float scp049InfectMultiplier;
[PipeField(false)]
public bool scp079CanOpen106;

// Gettable but not settable fields.
[PipeField]
public readonly float scp650BloodSodiumMultiplier;
[PipeField(true)]
public Role exitTeleportsToScp106;

Import Example

[PipeLink("plugin.id", "scp049InfectMultiplier")]
private readonly PipeField<float> scp049InfectMultiplier;

[PipeLink("plugin.id", "scp650BloodSodiumMultiplier")]
private readonly PipeField scp650BloodSodiumMultiplier;

...

public override void OnEnable()
{
  // All of the pipe imports are from the same plugin, so if one pipe is null, all are.
  if (scp049InfectMultiplier != null) 
  {
    // Because the field is a generic type, implicit casting can be used.  Be careful when using reference types with implicit casting, as a != null would check the value of the pipe and not the pipe itself.
    if (scp049InfectMultiplier < 2f) 
    {
      scp049InfectMultiplier.Value = 2f;
    }

    // A .Value and a cast must be used because it is not a generic type.
    Info("The value of " + nameof(scp650BloodSodiumMultiplier) + " is: " + (float) scp650BloodSodiumMultiplier.Value);

    // If this check was not here, an exception would be thrown from trying to set a readonly field.
    if (!scp650BloodSodiumMultiplier.Readonly)
    {
      scp650BloodSodiumMultiplier.Value = 1000f;
    }
  }
}

PipeProperty

Allows for other plugins to get/set a property marked by this attribute. Gettability/settability is determined by whether or not a getter/setter is public, however it can be overridden in the constructor.

Export Example

// Gettable and settable properties.
[PipeProperty]
public float Scp1730Duration { get; set; }
[PipeProperty(true, true)]
public bool Scp173IsOverrated { get; set; };

// Gettable but not settable properties.
[PipeProperty]
public float Scp939DefaultSpeed { get; private set; }
[PipeProperty(true, false)] // You should only use these if you the export is a non-public member.
public Role GodRole { get; set; }

// Settable but not gettable properties.
[PipeProperty]
public int DidntReadTheSmodWiki { private get; set; }
[PipeProperty(true, false)] // You should only use these if you the export is a non-public member.
public string DisplayText { get; set; }

Import Example

[PipeLink("plugin.id", "Scp1730Duration")]
private readonly PipeProperty<float> Scp1730Duration;

[PipeLink("plugin.id", "Scp939DefaultSpeed")]
private readonly PipeProperty Scp939DefaultSpeed;

[PipeLink("plugin.id", "DidntReadTheSmodWiki")]
private readonly PipeProperty<int> DidntReadTheSmodWiki;

...

public override void OnEnable()
{
  // All of the pipe imports are from the same plugin, so if one pipe is null, all are.
  if (Scp1730Duration != null) 
  {
    Scp1730Duration.Value *= 2f;

    // Again, .Value and a cast must be used because it is not a generic type.
    Info("The value of " + nameof(Scp939DefaultSpeed) + " is: " + (float) Scp939DefaultSpeed.Value);

    // If this check was not here, an exception would be thrown from trying to set a get-only field.
    if (!Scp939DefaultSpeed.Settable)
    {
      Scp939DefaultSpeed.Value = 1000f; // Nyoom
    }

    while (true)
    {
      DidntReadTheSmodWiki.Value++;
      if (DidntReadTheSmodWiki.Gettable)
      {
        Info(DidntReadTheSmodWiki + " people haven't read the ServerMod wiki.")
      }
    }
  }
}

PipeMethod

Allows for other plugins to call a method marked by this attribute. There is no parameter type restrictions during compile time, so exceptions may at run-time occur due to invalid parameter count or type.

Export Example

[PipeMethod]
public float CalculatePlayerNuisanceFactor(Player player)
{
  float factor;

  switch (player.TeamRole.Role)
  {
    case Role.FACILITY_GUARD:
      factor = 2f;
      break;

    case Role.SCIENTIST:
      factor = 1.5f;
      break;

    default:
      factor = 1f;
      break;
  }

  switch (player.GetHeldItem().ItemType)
  {
    case ItemType.COM15:
    case ItemType.USP:
    case ItemType.MP7:
    case ItemType.P90:
    case ItemType.E11_RIFLE:
    case ItemType.LOGICER:
      factor *= 2f;
      break;

    case ItemType.FLASHBANG:
      factor *= 4f;
      break;
  }

  return factor;
}

Import Example

[PipeLink("plugin.id", "CalculatePlayerNuisanceFactor")]
private readonly MethodPipe<float> CalculatePlayerNuisanceFactor;

...

public override void OnEnable()
{
  // If the plugin (and therefore the export pipe) exists.
  if (CalculatePlayerNuisanceFactor != null)
  {
    Player worst = Server.GetPlayers()
                         .OrderByDescending(x => CalculatePlayerNuisanceFactor.Invoke(x))
                         .FirstOrDefault();
    
    if (worst != null) 
    {
      Info("Greatest nuisance: " + worst.Name);
    }
  }
}

PipeEvent

Allows for other plugins to batch-call methods marked by the attribute and labeled with the proper event name.
The return value of an event is not used. If you need a value, pass a 1 element array that other plugins can set.
It's good practice to keep methods private in order to discourage other plugins from invoking whitelisted events or your plugin event specifically.

Export Example

[PipeEvent("courtney.example.plugin.Echo", // The event ID. The event ID consists of: plugin ID + "." + event name
           "courtney.example.plugin" // More plugin IDs can be added here to whitelist plugins to run specific events.
           )]
private void OnEcho(string text) => Info("Example plugin says: " + text);

[PipeEvent("plugin.id.CacheCheck")]
private void OnCacheCheck(string[] hashedIds, bool[] downloadFull)
{
  if (downloadFull[0]) return;

  foreach (string hash in File.ReadAllLines("customhashes.txt")) 
  {
    if (!hashedIds.Contains(hash)) 
    {
      downloadFull[0] = true;
      break;
    }
  }
}

Import Example

public void CheckCache()
{
  byte[] data = File.ReadAllBytes("hashes.txt");
  string allText = Encoding.UTF8.GetString(data);
  string[] hashes = string.Split('\n');

  // Single length array allows for get/set behavior on a parameter, much like a ref would. However, in, out, and ref is not supported.
  bool[] result = new[] {data.Length == ValidHashFileSize};
  InvokeEvent("CacheCheck", hashes, result);

  if (result[0])
  {
    hashes = DownloadAllHashes();
  }
  
  Hashes = hashes;
}

Server Guides

API Documents

Clone this wiki locally