Worked example: the 2.5x Kerbolar System
The 2.5x Kerbolar System mod is Redux's whole galaxy, Drast and Beyl included, at 2.5 times the size. The mod ships its own campaign pack. A player selects this pack when the player starts a campaign. A campaign that does not have the features of the mod on keeps the normal Redux galaxy without changes.
The mod has three files: a swinfo.json, one Patch Manager Lua file and a localization CSV. The full source is on GitHub. The excerpts on this page come from KerbolarUpscale.lua in version 0.6.0. The mod uses campaign features, patches and campaign packs together. Thus, it is a good example of how these items work together.
The plan
Declare two features:
Rescalefor the galaxy andAntennasfor the relay ranges.In the
Rescalefeature, patch these items in place:all bodies
the science regions and discoverables of each body
the orbits of the galaxy
the atmospheres and clouds
In the
Antennasfeature, increase the range of all transmitters.Add a campaign pack that turns on all of Redux and the two features.
The mod does not copy assets. A campaign with the features on gets the Redux galaxy with the patches applied. A campaign without the features does not run the patches.
Features
The mod declares its features in swinfo.json. Each feature requires the Redux feature that it changes:
"features": {
"Rescale": {
"requires": ["Ksp2Redux:Galaxy"]
},
"Antennas": {
"requires": ["Ksp2Redux:Parts"]
}
}The two features also require the default feature of the mod, KerbolarUpscale:KerbolarUpscale. This requirement is implicit. Because the mod has two features, a player can turn on one feature without the other. For example, a player can turn on the larger galaxy in the feature editor without the longer antenna ranges.
The Lua file starts with the constants that all patches use:
local PACK = "Kerbolar25x"
local RESCALE = 2.5
-- Atmospheres thicken more slowly than the bodies grow
local ATMOSPHERE_RESCALE = math.log((16.0 / 9.0) + ((2.0 * RESCALE) / 9.0)) / math.log(2.0)Bodies
The mod patches an explicit list of the Redux bodies. It does not use "*". Thus, a body that a different mod adds to the galaxy keeps its own data. The list is the RADII table. This table also gives the original radius of each body. The discoverables patch uses this radius.
Each patch is inside PM:WithFeature("Rescale", ...) and runs :Late(). Thus, each patch sees the content that Redux adds, for example Drast, Beyl and the discoverables of Kerbin:
PM:WithFeature("Rescale", function()
-- Kerbol and Jool have no discoverables, so those patches match nothing
for bodyName in pairs(RADII) do
local assetPrefix = string.lower(bodyName) .. "_science_regions"
PM.Planets:Patch("Rescale" .. bodyName):Late():Named(bodyName):Do(rescaleBody)
PM.Science:PatchRegions("Rescale" .. bodyName .. "Regions"):Late():Named(assetPrefix):Do(rescaleRegions)
PM.Science:PatchDiscoverables("Rescale" .. bodyName .. "Discoverables")
:Late()
:Named(assetPrefix .. "_discoverables")
:Do(rescaleDiscoverables)
end
-- ... the galaxy, atmosphere and cloud patches below sit in the same scope
end)rescaleBody makes these changes to each body:
It increases the radius.
It scales the terrain to agree with the radius.
It moves out all values that the game measures from the surface.
local function rescaleBody(body)
local oldRadius = body.radius
if body.radius ~= nil then
body.radius = body.radius * RESCALE
end
-- Terrain grows with the body, so mountains keep their shape rather than flattening into a sphere
body.TerrainHeightMultiplier = RESCALE
if body.MinTerrainHeight ~= nil then
body.MinTerrainHeight = body.MinTerrainHeight * RESCALE
end
if body.MaxTerrainHeight ~= nil then
body.MaxTerrainHeight = body.MaxTerrainHeight * RESCALE
end
-- The terrain is built this far below sea level, so it scales with the terrain to keep the shorelines in place
if body.oceanAltitude ~= nil then
body.oceanAltitude = body.oceanAltitude * RESCALE
end
if body.atmosphereDepth ~= nil then
body.atmosphereDepth = body.atmosphereDepth * ATMOSPHERE_RESCALE
end
if body.ForcedSphereOfInfluence ~= nil then
body.ForcedSphereOfInfluence = body.ForcedSphereOfInfluence * RESCALE
end
-- ... inverseRotThresholdAltitude, StarLuminosity, surface objects and atmosphere curves follow
endA few things are easy to miss:
TerrainHeightMultiplier: Without it the mountains keep their stock height on a body 2.5 times bigger, and small bodies like Drast come out nearly round.oceanAltitude: The terrain is built this far below sea level. Left at the stock value under 2.5 times taller terrain, the seabed rises out of the ocean and most of it disappears.ForcedSphereOfInfluence: Bodies with a forced sphere of influence keep it unless you scale it yourself.Objects placed on the surface, such as the space center's sim objects in
LocalSimObjectsData, are positions from the body's center. Move them out by the change in radius and keep their height above sea level. Scaling the whole position by 2.5 also multiplies their altitude, and the space center campus, which cannot grow, ends up hundreds of meters above its pad.StarLuminosity: It scales with the square of the distance, so Kerbol's goes up byRESCALE * RESCALEto keep the planets as warm as before.Atmosphere curves use altitude as the key. Thus, the patch multiplies their key times by
ATMOSPHERE_RESCALE.
Science
Region altitudes and discoverable positions are in meters. Thus, the same loop patches both of them. The regions patch multiplies the situation altitudes:
local function rescaleRegions(regions)
local situations = regions.SituationData
if situations == nil then
return
end
situations.HighOrbitMaxAltitude = situations.HighOrbitMaxAltitude * RESCALE
situations.LowOrbitMaxAltutude = situations.LowOrbitMaxAltutude * ATMOSPHERE_RESCALE
situations.AtmosphereMaxAltutude = situations.AtmosphereMaxAltutude * ATMOSPHERE_RESCALE
endEach discoverable moves out along its own direction by the change in radius. Thus, it keeps its height above the surface. This calculation uses the original radius of each body from RADII. The detection radius of each discoverable is multiplied by RESCALE.
The galaxy
A patch in the same Rescale scope increases the orbits. It patches the Redux galaxy in place:
PM.Planets:PatchDefaultGalaxy("RescaleOrbits"):Late():Do(function(galaxy)
for _, entry in ipairs(galaxy.CelestialBodies) do
local orbit = entry.OrbitProperties
if orbit ~= nil and orbit.semiMajorAxis ~= nil then
orbit.semiMajorAxis = orbit.semiMajorAxis * RESCALE
end
end
end)The mod does not add a second galaxy. The pack uses the Redux GalaxyDefinition_Default. This galaxy is the default value of the Galaxy field of a pack. The Rescale feature controls if the galaxy is rescaled.
Atmospheres and clouds
These patches are also in the Rescale scope. Each override that the mod creates or patches applies only in campaigns that have the feature on. Each of the five bodies with an atmosphere gets a new atmosphere override. The ATMOSPHERES table gives the dimensions:
for bodyName, atmosphere in pairs(ATMOSPHERES) do
PM.Planets:CreateAtmosphereOverride(bodyName, function(atmosphereOverride)
atmosphereOverride.BottomRadius = atmosphere.radius * RESCALE
atmosphereOverride.AtmosphereHeight = atmosphere.depth * ATMOSPHERE_RESCALE
atmosphereOverride.AbsorptionHeightMinMax = {
x = atmosphere.absorptionMin * ATMOSPHERE_RESCALE,
y = atmosphere.absorptionMax * ATMOSPHERE_RESCALE,
}
end)
endRedux already has tuned cloud overrides for Kerbin, Eve and Jool. The mod patches these overrides in place and moves the clouds up:
for _, bodyName in ipairs({ "kerbin", "eve", "jool" }) do
PM.Planets:PatchCloudOverride("RescaleClouds" .. bodyName)
:Late()
:Named("volume_cloud_override_" .. bodyName)
:Do(function(cloudOverride)
cloudOverride.planetRadius = CLOUD_PLANET_RADII[bodyName] * RESCALE
for _, cloudLayer in pairs(cloudOverride.cumulusList) do
scaleCloudLayer(cloudLayer)
end
end)
endLaythe and Duna have stock clouds. The mod creates new cloud overrides for them with PM.Planets:CreateCloudOverride. The layers of these overrides append the stock heights multiplied by ATMOSPHERE_RESCALE.
Antennas
The antenna patch is in its own feature. Thus, the antenna patch can be on while the rescale is off, or off while the rescale is on:
-- Antennas reach 2.5 times as far, so relays cover the wider orbits
PM:WithFeature("Antennas", function()
PM.Parts:Patch("RescaleAntennas")
:Late()
:Has("Module_DataTransmitter")
:Do(function(part)
local transmitter = part.Module_DataTransmitter.Data_Transmitter
transmitter.CommunicationRange = transmitter.CommunicationRange * RESCALE
end)
end)The game does not save antenna range with a vessel. Thus, craft in a 2.5x campaign get the longer range when they load.
The campaign pack
The pack turns on all of Redux and the two features of the mod. The pack does not list the default feature of the mod, because the two features require it:
-- The campaign pack: Redux's whole game, with its galaxy rescaled and the antennas stretched to match
PM.CampaignPacks:CreateCampaignPack(PACK, function(campaignPack)
campaignPack.Features = { "Ksp2Redux:All", "KerbolarUpscale:Rescale", "KerbolarUpscale:Antennas" }
campaignPack.CampaignPackDescriptionLocalizationKey = "CampaignPacks/Description/" .. PACK
end)The pack is the only item that the mod creates outside its features. The game shows campaign packs in all feature sets. Thus, the pack shows on the new campaign menu when the mod is installed.
Names
The pack's card reads its text from localization keys, shipped in localizations/kerbolar25x_loc.csv next to the Lua file:
Key,Type,Desc,English
CampaignPacks/Kerbolar25x,Text,,2.5x Kerbolar System
CampaignPacks/Description/Kerbolar25x,Text,,"The complete Redux experience in a Kerbolar System 2.5 times the size"The mod does not ship names for its features. Thus, the feature editor shows their ids, KerbolarUpscale:Rescale and KerbolarUpscale:Antennas. To give the features names, add Feature/KerbolarUpscale/Rescale/Name and Feature/KerbolarUpscale/Rescale/Description rows to the same CSV. See Naming features for players.
Checking it worked
Start a campaign from the pack. Then examine pm_summary.log:
The log has a section for the feature set of that campaign. The heading of the section lists its features.
This section shows all rescale patches as applied.
The section for a normal Redux campaign lists the same patches under Removed Patches with the status
FEATURE.
In game, do these checks:
From orbit, make sure that the radius and terrain of Kerbin are correct.
In the VAB, compare the range of an antenna with the range in a normal Redux campaign.