Science

PM.Science covers the science and progression systems: experiments, the science regions that classify where you are, the discoverables you find in the world, and the tech tree that spends science points to unlock parts. It is a moderate-sized module with a separate entry point per content kind.

The entry points

Science is several different assets, so there is a Patch method per kind rather than one:

  • PM.Science:PatchExperiments(name) patches science experiments.

  • PM.Science:NewExperiment(name, callback) creates a new experiment.

  • PM.Science:PatchRegions(name) patches science regions.

  • PM.Science:PatchDiscoverables(name) patches discoverables.

  • PM.Science:PatchTechNodes(name) patches tech-tree nodes.

  • PM.Science:NewTechNode(name, callback) creates a new tech node.

  • PM.Science:AddPartsToTechNode(nodeName, ...) is a shortcut for unlocking parts.

  • PM.Science:DuplicateExperiments, DuplicateRegions, DuplicateDiscoverables and DuplicateTechNodes copy each kind. See Copying an existing asset.

Experiments

PatchExperiments targets an experiment by id. A common edit is appending a flavor description for a location the experiment was missing. This real patch fixes a gap on Pol:

PM.Science:PatchExperiments("CrewObservationPolMidlandsFlavorFix")
          :Named("CrewObservation")
          :Do(function(experiment)
              experiment.DataFlavorDescriptions:Append({
                  ResearchLocationID = "Pol_Landed_PolMidlands",
                  LocalizationTag = "Science/Experiments/CrewObservation/Data/Flavor/Pol_Landed_PolMidlands"
              })
          end)

NewExperiment(name, callback) creates a whole new experiment, filling in its data and sample values, descriptions, and transmission size.

The tech tree

Tech nodes are where most science patches land, because reorganizing the tree and moving part unlocks is common. PatchTechNodes targets a node by id (tNode_2v_rockets_01 and so on). Inside, you edit the node's prerequisites, science cost, position, and unlocked parts. This real patch fully reworks a node

PM.Science:PatchTechNodes("2VRockets01Reorder")
          :Named("tNode_2v_rockets_01")
          :Do(function(node)
              node.RequiredTechNodeIDs = { "tNode_1.5v_rockets_02" }
              node.RequiredSciencePoints = 230
              node.UnlockedPartsIDs:RemoveWhere(|id| id == "booster_1v_solid_kickback")
              node.UnlockedPartsIDs:Append("adapter_2v_methalox_1.5v-2v")
              node.TierToUnlock = 0
              node.TechTreePosition = { x = 1600.0, y = 750.0 }
          end)

Note the list operations on UnlockedPartsIDs: :RemoveWhere to drop a part, :Append to add one, or a whole-list assignment to replace it. These are the JSON list methods.

Unlocking parts the easy way

Adding parts to a node is so common it has a dedicated shortcut. AddPartsToTechNode takes the node id and any number of part ids, and appends them to that node's unlock list:

PM.Science:AddPartsToTechNode("tNode_1v_rockets_02",
    "booster_0v_srb_mantis",
    "booster_0v_srb_shrimp")

This is equivalent to a PatchTechNodes that appends to UnlockedPartsIDs, just shorter. Use it when all you are doing is unlocking parts.

Unlocking variants

A node can also unlock V-SwiFT part variants. Its UnlockedVariants list names each variant by PartName, VariantSetId and VariantId. A variant that no node names is always selectable. If two nodes name the same variant, the first node that loads unlocks it. The R&D Center shows a node's variants after its parts, on a blue tile with a + badge.

Node JSON from an older bake has no UnlockedVariants list. Make the list before you append to it:

PM.Science:PatchTechNodes("MyMod_StripedTank")
    :Named("tNode_1v_rockets_01")
    :Do(function(node)
        if node.UnlockedVariants == nil then node.UnlockedVariants = J.Empty() end
        node.UnlockedVariants:Append({
            PartName = "fueltank_1v_inline_methalox_1x1",
            VariantSetId = "finish",
            VariantId = "striped",
        })
    end)

Creating tech nodes

NewTechNode(name, callback) creates a new node. The name is the node's ID. The node starts as the game writes one: every field has its default value, and every list is empty. The callback sets the fields, and can append to the lists directly. The call returns the new asset, so you can gate it with :Feature.

PM.Science:NewTechNode("tNode_1v_electric_aviation_01", function(node)
    node.NameLocKey = "MyMod/TechNodes/Names/tNode_1v_electric_aviation_01"
    node.DescriptionLocKey = "MyMod/TechNodes/Descriptions/tNode_1v_electric_aviation_01"
    node.IconID = "ICO-RDCenter-Plane-24x"
    node.RequiredSciencePoints = 100
    node.RequiredTechNodeIDs:Append("tNode_1v_aero_02")
    node.TechTreePosition.x = 1080
    node.TechTreePosition.y = 90
    node.UnlockedVariants:Append({
        PartName = "engine_1v_turbofan_methane_wheesley",
        VariantSetId = "propulsion",
        VariantId = "electric",
    })
end):Feature("TechTree")

The node's name and description keys need rows in your mod's localization CSV.

Regions and discoverables

PatchRegions patches science regions, the zones that set the per-situation science multipliers for a body. PatchDiscoverables patches discoverables, the anomalies you find in the world, keyed by region id with a position and detection radius. Both are indexed lists, so you reach an entry by its id and edit it. Their field forms are in pm-science.d.lua.

Features

Science content belongs to the Redux campaign features:

Content

Feature

Tech nodes

Ksp2Redux:Nodes

Experiments

Ksp2Redux:Science

Science regions and discoverables

Ksp2Redux:Galaxy, with the bodies they describe

  • To give a campaign pack its own tech tree, create its nodes and gate them on one of your features. If the stock tree must not load with it, do not put Ksp2Redux:Nodes in the features of the pack.

  • To change regions or discoverables for some campaigns only, patch them in place under a feature. A rescaled body usually needs changes to both, because region altitudes and discoverable positions are in meters:

PM:WithFeature("Rescale", function()
    PM.Science:PatchRegions("RescaleKerbinRegions")
              :Late()
              :Named("kerbin_science_regions")
              :Do(function(regions)
                  regions.SituationData.HighOrbitMaxAltitude = regions.SituationData.HighOrbitMaxAltitude * 2.5
              end)
end)

The Duplicate... methods copy an asset under a new name. The copy keeps the internal ids of the asset. Thus a copy of the regions of a body is a second definition of the same regions. Use these methods only to start new content.