Arm Upgrades - NeisesMike/VehicleFramework GitHub Wiki
Arm upgrades are the most complex type of upgrade.
Unlike other upgrades, they require you to supply an arm model. At least for now.
Unless you specify otherwise during registration, ModVehicleArm upgrades will also be made available for the Prawn.
To implement an Arm upgrade, implement the abstract class
VehicleFramework.UpgradeTypes.ModVehicleArm
That class implements ModVehicleUpgrade, with one important override:
public override QuickSlotType QuickSlotType => QuickSlotType.Selectable;
Don't change this!
ModVehicleArm
, unlike other upgrade types, introduces a new abstract field over ModVehicleUpgrade
.
public abstract IEnumerator GetArmPrefab(IOut<GameObject> arm);
This is how you will supply your arm model, as a GameObject, to VehicleFramework.
Here is an example from the Scanner Arm:
public override IEnumerator GetArmPrefab(IOut<GameObject> arm)
{
// get the prefab from an AssetBundle in the file system
string directoryPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
string bundlePath = Path.Combine(directoryPath, "scanner");
var bundle = AssetBundle.LoadFromFile(bundlePath);
var objectArray = bundle.LoadAllAssets();
GameObject model = null;
foreach (System.Object obj in objectArray)
{
if (obj.ToString().Contains("ScannerArm"))
{
model = (GameObject)obj;
}
}
// Add an original "ScannerArm" component to the arm model.
model.AddComponent<ScannerArm>();
// Apply Subnautica's main shader to the model.
Shader shader = Shader.Find("MarmosetUBER");
foreach (var renderer in model.gameObject.GetComponentsInChildren<Renderer>(true))
{
foreach (Material mat in renderer.materials)
{
// give it the marmo shader, no matter what
mat.shader = shader;
}
}
// set the out parameter of this method to the arm model.
arm.Set(model);
// unload the asset bundle
bundle.Unload(false);
// exit the method
yield break;
}
The Arm upgrade has many new methods over ModVehicleUpgrade
. First let's look at the "normal" methods. Then we'll look at some that are specific to the Prawn (Exosuit).
public virtual bool OnArmDown(ArmActionParams param, out float cooldown) // return true OnUseSuccess (false if you refuse to do the action)
{
Logger.Log("Selecting arm: " + ClassId + " on ModVehicle: " + param.vehicle.subName.name + " in slotID: " + param.slotID.ToString());
cooldown = 0;
return true;
}
public virtual bool OnArmHeld(ArmActionParams param, out float cooldown) // return true OnHoldSuccess (false if you don't have "hold" logic)
{
Logger.Log("Holding arm: " + ClassId + " on ModVehicle: " + param.vehicle.subName.name + " in slotID: " + param.slotID.ToString());
cooldown = 0;
return true;
}
public virtual bool OnArmUp(ArmActionParams param, out float cooldown) // return true OnReleaseSuccess (basically always)
{
Logger.Log("Releasing arm: " + ClassId + " on ModVehicle: " + param.vehicle.subName.name + " in slotID: " + param.slotID.ToString());
cooldown = 0;
return true;
}
public virtual bool OnArmAltUse(ArmActionParams param)
{
Logger.Log("Releasing arm: " + ClassId + " on ModVehicle: " + param.vehicle.subName.name + " in slotID: " + param.slotID.ToString());
return false;
}
For my own convenience, I'm going to describe the above methods as if the player is using mouse + keyboard.
-
OnArmDown
happens when the player left clicks (for a left arm) or right clicks (for a right arm). -
OnArmUp
happens when the player releases their click. -
OnArmHeld
happens every frame the mouse is clicked down and held. So you typically get one invocation ofOnArmDown
, then multiple invocations ofOnArmHeld
, and one final invocation ofOnArmUp
. -
OnArmAltUse
happens when the player clicks while holding the alt-key (I think). The Prawn propulsion cannon is the only vanilla arm that does this. Read this excerpt: "It also shares the regular Propulsion Cannon's ability to take items from storage, though they will be taken from the Prawn Suit's storage rather than the Player's, and the command is alt+F as opposed to just F."
Here is the definition of the SelectableActionParams
struct:
public struct ArmActionParams
{
public Vehicle vehicle;
public int slotID;
public TechType techType;
public GameObject arm;
}
It's a lot like AddActionParams
, so look at the ModVehicleUpgrade
guide to understand more.
Here is an example from the Scanner Arm:
public override bool OnArmDown(ArmActionParams param, out float cooldown)
{
param.arm.GetComponent<ScannerArm>().StartUsing();
cooldown = 0;
return true;
}
public override bool OnArmUp(ArmActionParams param, out float cooldown)
{
param.arm.GetComponent<ScannerArm>().StopUsing();
cooldown = 0;
return true;
}
public override bool OnArmHeld(ArmActionParams param, out float cooldown)
{
cooldown = 0;
return false;
}
public override bool OnArmAltUse(ArmActionParams param)
{
return false;
}
Notice the Scanner Arm makes no use of the cooldown
out parameter. This could be useful if you want to implement something like a torpedo launcher, where you want to enforce a delay between shots of the gun.
public virtual void Update(GameObject arm, Vehicle vehicle, ref Quaternion aimDirection)
{
// take action every frame, and possible update the arm's Aim Direction.
}
public virtual void OnPilotExit(GameObject arm, Vehicle vehicle)
{
// reset the arm if need be
}
public virtual GameObject GetInteractableRoot(GameObject arm, Vehicle vehicle, GameObject target)
{
// target is the camera's LookTarget.
// the return value becomes Exosuit.activeTarget,
// which influences OnHandHover GUI notifications,
// and can be useful in arm logic
return null;
}