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,DuplicateDiscoverablesandDuplicateTechNodescopy 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 |
|
Experiments |
|
Science regions and discoverables |
|
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:Nodesin 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.