Skip to content

Finding Registered UpgradeHandlers

PrimeSonic edited this page Oct 21, 2019 · 5 revisions

Originally introduced in the 3.0 update and later refined in the 4.0 overhaul, MoreCyclopsUpgrades can now be used as a public API, allowing other mods to integrate their own cyclops upgrade modules and have them be fully compatible with the Auxiliary Upgrade Console.


Finding Common UpgradeHandlers

While ideally you'd want your UpgradeHandler to be simple and self-contained, it's more likely that you have some other, more interesting component, that either gets activated or otherwise affected by that current status of your UpgradeHandler.
For example, for the Cyclops to know if it is allowed to start recharging from thermal energy, it needs to know if a Thermal Reactor Upgrade Module is currently installed.

To do this, MCUServices exposes the Find interface which contains methods you can use to locate anything previously registered, like your UpgradeHandlers, once they've been created.

// Remember to include these namespaces
using MoreCyclopsUpgrades.API;
using MoreCyclopsUpgrades.API.Upgrades;

/// <summary>
/// Gets the upgrade handler at the specified Cyclops sub for the specified upgrade module <see cref="TechType"/>.<para/>
/// Use this if you need to obtain a reference to your <seealso cref="UpgradeHandler"/> for something else in your mod.
/// </summary>        
/// <param name="cyclops">The cyclops to search in.</param>
/// <param name="upgradeId">The upgrade module techtype ID.</param>
/// <returns>An <see cref="UpgradeHandler"/> if found by techtype; Otherwise returns null.</returns>
UpgradeHandler CyclopsUpgradeHandler(SubRoot cyclops, TechType upgradeId);

Note: To avoid possible race conditions involved in the initialization of the various Cyclops components and managers, it is strongly recommended to use Lazy Loading pattern whenever you want to store a reference to your UpgradeHandler.
Since Subnautica is limited to .NET 3.5, we do not have the Lazy wrapper.
However, you can still implement a simple Lazy Loading Pattern yourself like this:

internal class MyComponent
{
    // Let's assume you already have a reference to the Cyclops sub and the upgrade TechType
    private SubRoot cyclops;
    private TechType myUpgradeTechType;

    // This private field will hold a reference to your UpgradeHandler,
    // but it starts out null when the component is first instantiated
    private UpgradeHandler myUpgradeHandler = null;

    // This get-only property is what you would actually use to check on your UpgradeHandler
    internal UpgradeHandler MyUpgradeHandler
    {
        get 
        {
             if (myUpgradeHandler == null)
             {
                 myUpgradeHandler = MCUServices.Find.CyclopsUpgradeHandler(cyclops, myUpgradeTechType)
             }

             return myUpgradeHandler;
        }
    }
}

This example can actually be written in even less code, making use of the syntactic sugar provided by Expression Bodied Members and Null Coalescing.

internal class MyComponent
{
    private SubRoot cyclops;
    private TechType myCoolUpgradeTechType;

    private UpgradeHandler myUpgradeHandler = null;
    internal UpgradeHandler MyUpgradeHandler => myUpgradeHandler ?? (myUpgradeHandler = MCUServices.Find.CyclopsUpgradeHandler(cyclops, myUpgradeTechType));
}

Finding Custom UpgradeHandlers

By now you should be well aware that the UpgradeHandler class, while robust in the features it offers, might not be enough to fully encapsulate what you want to do.
With this in mind, the API encourages you to create your own classes that extend UpgradeHandler and add whatever you need.
For these cases, when you want a reference to your custom class, not the basic UpgradeHandler.
For these cases, the API offers a generic typed method you can use to fetch your custom upgrade handler class that gives you compile the time safety to ensure you didn't miss anything.

// Remember to include these namespaces
using MoreCyclopsUpgrades.API;
using MoreCyclopsUpgrades.API.Upgrades;

/// <summary>
/// Gets the upgrade handler at the specified Cyclops sub for the specified upgrade module <see cref="TechType"/>.<para/>
/// Use this if you need to obtain a reference to your <seealso cref="UpgradeHandler"/> for something else in your mod.
/// </summary>
/// <typeparam name="T">The class created by the <seealso cref="CreateUpgradeHandler"/> you passed into <seealso cref="IMCURegistration.CyclopsUpgradeHandler(CreateUpgradeHandler)"/>.</typeparam>
/// <param name="cyclops">The cyclops to search in.</param>
/// <param name="upgradeId">The upgrade module techtype ID.</param>
/// <returns>A type casted <see cref="UpgradeHandler"/> if found by techtype; Otherwise returns null.</returns>
T CyclopsUpgradeHandler<T>(SubRoot cyclops, TechType upgradeId) where T : UpgradeHandler;

Let's update the previous example to use a custom upgrade handler class instead of the basic UpgradeHandler.
It's really that simple.

internal class MyUpgradeHandler : UpgradeHandler
{
    ...
}
internal class MyComponent
{
    private SubRoot cyclops;
    private TechType myCoolUpgradeTechType;

    // The type is now your custom upgrade handler class
    private MyUpgradeHandler myUpgradeHandler = null;
    internal MyUpgradeHandler MyUpgradeHandler => 
                myUpgradeHandler ??
                (myUpgradeHandler = MCUServices.Find.CyclopsUpgradeHandler<MyUpgradeHandler>(cyclops, myUpgradeTechType));
    // Notice that all we did was add <MyUpgradeHandler> to the CyclopsUpgradeHandler method
}

Remember to check for null!
If you are calling MCUServices.Find from a Monobehavior or some other component capable of being active on its own,
then you absolutely must check for nulls.
If the Upgrade Handlers aren't ready by the time you are calling into MCUServices.Find, then you will get a null return value.
Remember, the Cyclops is loaded in parts, and it goes through a full upgrade cycle when loading in.
So treat a null UpgradeHandler as simply "not ready yet".

Clone this wiki locally
  翻译: