Upgrade Developer Manual - NeisesMike/VehicleFramework GitHub Wiki

Vehicle Framework supports adding upgrade modules for ModVehicles as well as the Seamoth, Prawn, and Cyclops all at once!

Upgrades change the functionality of the vehicle somehow. There's wide variety.

There are several distinct types of upgrade, and I will describe them here.

You can view their source in the repo, here.

This page will have information common to all ModVehicleUpgrades, but here are specific guides:

For now, Cyclops upgrades may only be of the "passive" type.

This page serves as the guide for Passive modules.

Overview

The base upgrade class has many virtual fields, but only three abstract fields:

namespace VehicleFramework.UpgradeTypes
{
  public abstract class ModVehicleUpgrade
  {
    public abstract string ClassId { get; }
    public abstract string DisplayName { get; }
    public abstract string Description { get; }
  }
}

That means the only thing you are required to do when implementing a ModVehicleUpgrade is to supply a ClassId, DisplayName, and Description.

  • ClassId: This is a unique name. It will be used for the spawn command. For example, spawn solarcharger
  • DisplayName: This is the name that will be shown in the PDA and crafting menus. It doesn't have to be unique.
  • Description: This text will be shown in the PDA and crafting menus. It should tell about what your upgrade is or does.

But if you only implement these, your upgrade probably will not be very interesting!

Registration

Registration can happen in a single line inside your main patch. See this example from the SolarCharging upgrade:

using BepInEx;

namespace SolarChargingModule
{
    public class SolarChargingModule: ModVehicleUpgrade
    {
        // implementation
    }
    [BepInPlugin("com.mikjaw.subnautica.solarchargingmodule.mod", "SolarChargingModule", "1.2")]
    [BepInDependency("com.snmodding.nautilus")]
    [BepInDependency(VehicleFramework.PluginInfo.PLUGIN_GUID, VehicleFramework.PluginInfo.PLUGIN_VERSION)]
    public class MainPatcher : BaseUnityPlugin
    {
        public void Start()
        {
            VehicleFramework.Admin.UpgradeRegistrar.RegisterUpgrade(new SolarChargingModule());
        }
    }
}

By default, your upgrade will be made available for all ModVehicles, Seamoths, and Prawns. But you might not want that. So you can register in a slightly different way. See this example for the DroneRange upgrade that can only be accepted by ModVehicles.

using BepInEx;
namespace DroneRange
{
    public class DroneRangeUpgrade : ModVehicleUpgrade
    {
        // implementation
    }
    [BepInPlugin("com.mikjaw.subnautica.dronerange.mod", "DroneRange", "1.2")]
    [BepInDependency("com.snmodding.nautilus")]
    [BepInDependency(VehicleFramework.PluginInfo.PLUGIN_GUID, VehicleFramework.PluginInfo.PLUGIN_VERSION)]
    public class MainPatcher : BaseUnityPlugin
    {
        public void Start()
        {
            VehicleFramework.Admin.UpgradeCompat compat = new VehicleFramework.Admin.UpgradeCompat
            {
                skipCyclops = true,
                skipModVehicle = false,
                skipSeamoth = true,
                skipExosuit = true
            };
            VehicleFramework.Admin.UpgradeRegistrar.RegisterUpgrade(new DroneRangeUpgrade(), compat);
        }
    }
}

Virtual Fields

The ModVehicleUpgrade class also has many virtual fields. You can override these if you like. Let's start with the most important ones and work our way down through them all.

public virtual List<Assets.Ingredient> Recipe => new List<Assets.Ingredient> { new Assets.Ingredient(TechType.Titanium, 1) };

There isn't much to say about the recipe. This is how the player will build the upgrade.

public virtual void OnAdded(AddActionParams param)

This is a method that will be invoked when the upgrade is added to a vehicle. Here is the definition of the AddActionParams struct:

public struct AddActionParams
{
    public Vehicle vehicle;
    public SubRoot cyclops;
    public int slotID;
    public TechType techType;
    public bool isAdded;
}

Either vehicle is null and cyclops is not OR cyclops is null and vehicle is not. But you don't really have to worry about that. See the "OnCyclops" method below, which is the only time you'll need to worry about the cyclops field.

Here is an example of how the SolarChargingModule uses this method:

public override void OnAdded(AddActionParams param)
{
    param.vehicle.gameObject.EnsureComponent<VFSolarCharger>().numChargers = param.vehicle.GetCurrentUpgrades().Where(x => x.Contains("SolarChargingModule")).Count();
}

So clearly param.vehicle is a critical field. Please note that param.vehicle is of type Vehicle and not of type ModVehicle. This is for compatibility with the Seamoth and Prawn!

public virtual void OnRemoved(AddActionParams param)

This is a method that will be invoked when the upgrade is removed from a vehicle. See the OnAdded section above.

public virtual void OnCyclops(AddActionParams param)

This is a method that will be invoked when the upgrade is added to a Cyclops. The Cyclops is very different from other vehicles, so it has this method that is distinct from OnAdded and OnRemoved. OnCyclops is called when the upgrade is added and when it is removed. The only difference is that param.isAdded will be true and false, respectively.

public virtual QuickSlotType QuickSlotType => QuickSlotType.Passive;

This determines what type of upgrade your ModVehicleUpgrade will be. There are several types:

  • Passive: takes action only when added or removed
  • Selectable: like Passive, but can be activated by the player
  • SelectableChargeable: like Selectable, but can be charged
  • Toggleable: like Passive, but takes repeated action as long as the Player has it activated

public virtual Atlas.Sprite Icon => MainPatcher.UpgradeIcon;

Give your ModVehicleUpgrade a distinct icon that will appear in the PDA and crafting menu.

public virtual Color Color => Color.red;

Ha ha, make the physical upgrade take on a different color scheme 😎

public virtual float CraftingTime => 3f;

Make the fabricator take a bit longer or shorter to finish building the upgrade.

public virtual bool UnlockAtStart => true;

Override this to false if you want the player to have to unlock your ModVehicleUpgrade during gameplay.

public virtual TechType UnlockWith => TechType.Constructor;

Override this to specify with what other unlockable your ModVehicleUpgrade should be unlocked. By default, upgrades that are not unlocked at start will be unlocked when the constructor (mobile vehicle bay) is unlocked.

public virtual string UnlockedMessage => "New vehicle upgrade acquired";

If the player unlocks your ModVehicleUpgrade during gameplay, this text will appear in the pop-up window when they do so.

public virtual Sprite UnlockedSprite => null;

If the player unlocks your ModVehicleUpgrade during gameplay, this image will appear in the pop-up window when they do so.

public virtual string TabName { get; set; } = string.Empty;

If you want to create a single tab inside the relevant crafting tree branch, override this to create a unique name for that crafting tab. You'll also need to override TabDisplayName and optionally TabIcon. If you want more control over the crafting tree and where your upgrade will be placed, please see the Special Craft Tree Handling section below!

public virtual string TabDisplayName => string.Empty;

Override this to give a descriptive display name to the tab you've designated by overriding TabName.

public virtual Atlas.Sprite TabIcon => StaticAssets.UpgradeIcon;

Override this if you like to provide a special image for the tab you've designated by overriding TabName.

Instance Fields

public UpgradeTechTypes TechTypes { get; internal set; }

This field has all the techtypes for all versions of your upgrade. There's a different techtype for ModVehicles, Seamoths, and Prawns.

public List<CraftData.Ingredient> GetRecipe()

This method returns the recipe for your upgrade. It only differs from Recipe when ExtendRecipe has been used.

public void ExtendRecipe(Assets.Ingredient ingredient)

This method can be used to add ingredients to your Recipe but when you don't know the techtype for the ingredient at compile-time. This is useful to make multi-level upgrades that use previous versions as ingredients. For example, depth 2 uses depth 1 as an ingredient.

public bool HasTechType(TechType tt)

This method returns whether the input techtype is any techtype associated with your upgrade. So if you input the Seamoth version's techtype, it returns true. If you input the ModVehicle or Prawn techtypes, it also returns true.

public int GetNumberInstalled(Vehicle vehicle)

It does as it says. It can be a convenient way to count the number of like modules in a vehicle, which is useful for upgrades that stack.

Special Craft Tree Handling

All VF upgrades are now housed in the VF Fabricator. The above ways of doing things are still fine, and if you use them then VF will decide how to place your upgrades in the crafting tree. But if you want special handling or if you want to add your own crafting tabs, read on.

New ModVehicleUpgrade fields:

public virtual bool IsVehicleSpecific => false;
public virtual List<CraftingNode> CraftingPath { get; set; } = null;

If you override IsVehicleSpecific to true, then your UpgradeCompat argument will be ignored and your upgrade will only be made for ModVehicles and it will be placed in the "Custom" tab at the base level of the VF Fabricator's crafting tree. That tab is labeled "Vehicle Specific Upgrades."

If you do not override CraftingPath, then upgrade.TabName, upgrade.TabDisplayName, upgrade.TabIcon will be used to create the tab as usual.

If you want to create a system of nested tabs, for example if your vehicle has different groups of upgrades, then you can override CraftingPath. Such an override might look like this:

return new List<CraftingNode>
{
    new CraftingNode
    {
        name = "myinternaltabname",
        displayName = "My Proud Display Name",
        icon = VehicleFramework.Assets.SpriteHelper.GetSprite("myTabIcon.png")
    }
};

But that would be just like only setting the upgrade.TabName and upgrade.TabDisplayName and upgrade.TabIcon. Another example is this:

return new List<CraftingNode>
{
    new CraftingNode
    {
        name = "myinternaltabname",
        displayName = "My Proud Display Name",
        icon = VehicleFramework.Assets.SpriteHelper.GetSprite("myTabIcon.png")
    },
    new CraftingNode
    {
        name = "mysecondinternaltabname",
        displayName = "My Second Proud Display Name",
        icon = VehicleFramework.Assets.SpriteHelper.GetSprite("mySecondTabIcon.png")
    }
};

And this would create two crafting tabs, one inside the other.

Please Note! Every crafting tab must contain only tabs or only crafting nodes. Tabs and crafting nodes may not be mixed! If you try to do so, VF will alert you in the log to this error and prevent your upgrade from being registered.