Skip to content

BetterCargoPartVolume

gotmachine edited this page Nov 3, 2021 · 6 revisions

This is unreleased and out of date

Automatic ModuleCargoPart volume generation

This patch will generate a value for the ModuleCargoPart.packedVolume field if it set to 0 or isn't defined in the module configuration. This works by getting the bounds of the part prefab active mesh renderers or colliders, after part compilation. Generated volumes are logged to ksp.log the first time, and cached in the PluginData\BetterCargoPartVolume.cfg file.

ModulePartVariants volume switching

For parts that don't have a ModuleCargoPart.packedVolume defined and have a ModulePartVariants, a separate volume will be generated for every VARIANT that use a GAMEOBJECTS definition. Those separate volumes will be used when the part instance is put in an inventory.

The volume for a variant can be explicitly defined (preventing it from being auto-generated) by adding an EXTRA_INFO sub-node to the VARIANT node, and adding a packedVolume = x value (x in liters) to that subnode. Example :

MODULE
{
  name = ModuleCargoPart
  // make sure packedVolume isn't defined or is 0
}

MODULE
{
  name = ModulePartVariants
  baseVariant = Short
  VARIANT
  {
    name = Short
    GAMEOBJECTS
    {
      Shroud1x0 = true
      Shroud1x1 = false
    }
    EXTRA_INFO
    {
      packedVolume = 200
    }
  }
  VARIANT
  {
    name = Long
    GAMEOBJECTS
    {
      Shroud1x0 = false
      Shroud1x1 = true
    }
    EXTRA_INFO
    {
      packedVolume = 400
    }
  }
}

Variable packedVolume API for PartModules

The BetterCargoPartVolume patch allow other plugins to implement a PartModule interface for handling packedVolume modifications at runtime. This allow plugins that alter the shape of a part (ex : Tweakscale, B9PartSwitch...) to properly handle the volume of cargo parts.

Two instance methods must be implemented :

  • private/public bool CFAPI_UseMultipleVolumes() : must return true if that specific part instance can have multiple cargo volumes. If this is set to false, BetterCargoPartVolume will auto-generate a single packedVolume for that part, and the CFAPI_CurrentCargoVolume() method won't be used for that part. Note that this called on the part prefab, after the part compilation. Changing that value on part instances has no effect. This is purely for optimization purposes, to prevent useless processing. If your module is using multiple volumes most of the time, you can just always return true.
  • private/public float CFAPI_CurrentCargoVolume() : must return the volume in liters, or -1 if the part can't be stored in an inventory in its current state. This will be called on running instances, both in the editors and in flight. Note that when that method is called, the part will be in an invalid state due to how stock processes about-to-be-stored parts. So you must either :
    • Use pre-configured values for each shape volume
    • Generate volumes during prefab compilation, and save the values
    • Rely on scaling formulas

Example :

public class MyPartShapeChangingModule : PartModule
{
    private List<ShapeDefinition> shapes;
    private ShapeDefinition currentShape;

    // return a volume in liters, or -1 if you want to prevent
    // the part from being storable in inventories (it will still
    // be manipulable in EVA construction mode)
    private float CFAPI_CurrentCargoVolume()
    {
        return currentShape.packedVolume;
    }

    private bool CFAPI_UseMultipleVolumes()
    {
        foreach (ShapeDefinition shape in shapes)
        {
            if (shape.isChangingVolume)
                return true;
        }
        return false;
    }
}

To generate volumes, you can either roll your own implementation, but for consistency it is recommended that you call the BetterCargoPartVolume method. Example implementation :

public class MyPartShapeChangingModule : PartModule
{
    // That field will be populated by KSPCommunityFixes, if its AutomaticCargoPartVolume patch is enabled
    private static Func<Part, float> CFAPI_GetPackedVolumeForPart;

    public override void OnLoad(ConfigNode node)
    {
        // Execute only during prefab (re)compilation
        if (CFAPI_GetPackedVolumeForPart != null && (HighLogic.LoadedScene == GameScenes.LOADING || PartLoader.Instance.Recompile))
        {
            foreach (ShapeDefinition shape in shapes)
            {
                // Setup the part transforms/meshes/scale according to your module logic 
                shape.Activate();

                // Call the volume-getting method on the part prefab
                shape.packedVolume = CFAPI_GetPackedVolumeForPart(part);

                // If the method fails to compute a valid volume, it will return -1f.
                // You can use that value safely, this will prevent the part from being stored
                // in an inventory, and it will still be manipulable in EVA construction mode
                if (shape.packedVolume <= 0f)
                {
                   Debug.LogWarning($"Couldn't generate cargo volume for shape {shape.name}");
                }
            }
        }
    }
}

Misc notes

  • The patch override the stock implementation of ModuleInventoryPart.UpdateCapacityValues to rely on the stored part ProtoPartSnapshot for mass and volume, instead of using the part prefab mass/volume :
    • This is necessary to handle packedVolume switching on instances. For that to work, the patch force the ModuleCargoPart.packedVolume KSPField to be persisted.
    • This fixes tons of issues where the part instance mass doesn't match the part prefab mass (modified resources, modules implementing IPartMassModifier...)
  • The patch override the stock implementation of ModuleCargoPart.GetInfo(), showing packed volume: variable instead of a value when the part use a module implementing volume switching.
  • The patch remove the ability to switch variants from the cargo part icon when the part is stored in an inventory and make use of volume switching
  • The volume cache in PluginData\BetterCargoPartVolume.cfg will be invalidated and rebuilt when a config change is detected by ModuleManager