The router has carried vein_toggle, vein_ridged and vein_gap since the whole of it was parsed, and nothing read them. So the copper and iron mega-veins -- the long branching sheets that run through the deepslate and the copper band, not the small scattered ore blobs -- did not exist. OreVeinifier is not a decoration feature. It is the second entry of the same MaterialRuleList the aquifer heads: the aquifer answers first, and only where it says the position is solid rock does the veinifier get a turn at what would otherwise be plain stone. That is why a vein never opens into a cave, and it is why this lands in the density pass rather than in decorate. Three random draws per position, in a fixed order, from a factory hashed off "minecraft:ore": solidness, then richness, then the rare raw-ore block. Reordering them or hoisting the vein_gap compute above the second draw would change every vein in the world, so the code follows the bytecode's order rather than the one that reads better. The Y windows are the sharp edge worth knowing about: a vein's type comes from the sign of the veininess noise, but its window comes from the type, so veininess <= 0 anywhere outside -60..-8 is simply not a vein. Over 49 sampled chunks copper lands in 0..49 and iron in -57..-8, and raw ore comes out at 1.9% of ore blocks against vanilla's 2%.
103 lines
4.2 KiB
Go
103 lines
4.2 KiB
Go
package worldgen
|
|
|
|
import "math"
|
|
|
|
// orevein.go ports net.minecraft.world.level.levelgen.OreVeinifier.
|
|
//
|
|
// Ore veins are not a decoration feature: they run during the density pass, as
|
|
// the second entry of the same MaterialRuleList the aquifer heads. Where the
|
|
// aquifer says "this position is solid rock", the veinifier gets a chance to
|
|
// replace what would have been plain stone with copper or iron and its
|
|
// surrounding filler. That is why veins are long branching sheets rather than
|
|
// blobs, and why they never open into a cave: a position the aquifer hollowed
|
|
// out never reaches this code.
|
|
|
|
// Vein constants, as widened from OreVeinifier's float fields. Written out
|
|
// rather than computed so the double each float comparison widens to is
|
|
// unambiguous.
|
|
const (
|
|
veininessThreshold = 0.4000000059604645 // (double)(float)0.4f
|
|
edgeRoundoffBegin = 20.0 // an int field, used as a double
|
|
maxEdgeRoundoff = -0.2 // a real double literal, not a widened float
|
|
minRichness = 0.10000000149011612 // (double)(float)0.1f
|
|
maxRichness = 0.30000001192092896 // (double)(float)0.3f
|
|
maxRichnessThreshold = 0.6000000238418579 // (double)(float)0.6f
|
|
skipOreIfGapNoiseIsBelow = -0.3000000119209290 // (double)(float)-0.3f
|
|
|
|
veinSolidness float32 = 0.7 // compared as a float
|
|
chanceOfRawOreBlock float32 = 0.02 // compared as a float
|
|
)
|
|
|
|
// veinType is OreVeinifier.VeinType. Note the asymmetry: copper's ore is the
|
|
// plain stone variant while iron's is the deepslate one, and neither switches
|
|
// with depth — the block is fixed per type.
|
|
type veinType struct {
|
|
ore, rawOreBlock, filler uint16
|
|
minY, maxY int
|
|
}
|
|
|
|
var (
|
|
veinCopper = veinType{ore: 25313, rawOreBlock: 29578, filler: 2, minY: 0, maxY: 50}
|
|
veinIron = veinType{ore: 132, rawOreBlock: 29577, filler: 23452, minY: -60, maxY: -8}
|
|
)
|
|
|
|
// OreVeinifier decides whether a solid position becomes part of an ore vein.
|
|
type OreVeinifier struct {
|
|
toggle, ridged, gap DensityFunction
|
|
random PositionalRandomFactory
|
|
}
|
|
|
|
// NewOreVeinifier returns nil when the settings or the router leave veins off,
|
|
// which the caller reads as "always place the default block".
|
|
func NewOreVeinifier(od *OverworldDensity) *OreVeinifier {
|
|
if !od.OreVeinsEnabled || od.VeinToggle == nil || od.VeinRidged == nil || od.VeinGap == nil || od.OreRandom == nil {
|
|
return nil
|
|
}
|
|
return &OreVeinifier{toggle: od.VeinToggle, ridged: od.VeinRidged, gap: od.VeinGap, random: od.OreRandom}
|
|
}
|
|
|
|
// Calculate returns the block a vein places at this position, or ok=false to
|
|
// leave the default block. ctx must carry the cell-interpolated values: both
|
|
// vein_toggle and vein_ridged are minecraft:interpolated in the datapack, while
|
|
// vein_gap is a plain per-block noise.
|
|
//
|
|
// The three random draws happen in a fixed order and are the whole shape of the
|
|
// output; reordering them, or hoisting the vein_gap compute above the second
|
|
// draw, changes every vein in the world.
|
|
func (o *OreVeinifier) Calculate(ctx FunctionContext, x, y, z int) (uint16, bool) {
|
|
veininess := o.toggle.Compute(ctx)
|
|
vein := &veinIron
|
|
if veininess > 0 {
|
|
vein = &veinCopper
|
|
}
|
|
// The type comes from the sign of the noise but the Y window comes from the
|
|
// type, so a position outside its type's band is simply not a vein — which
|
|
// is what keeps copper above 0 and iron below -8.
|
|
distanceToTop := vein.maxY - y
|
|
distanceToBottom := y - vein.minY
|
|
if distanceToBottom < 0 || distanceToTop < 0 {
|
|
return 0, false
|
|
}
|
|
edgeDistance := min(distanceToTop, distanceToBottom)
|
|
edgeRoundoff := clampedMap(float64(edgeDistance), 0.0, edgeRoundoffBegin, maxEdgeRoundoff, 0.0)
|
|
absVeininess := math.Abs(veininess)
|
|
if absVeininess+edgeRoundoff < veininessThreshold {
|
|
return 0, false
|
|
}
|
|
|
|
random := o.random.At(x, y, z)
|
|
if random.NextFloat() > veinSolidness {
|
|
return 0, false
|
|
}
|
|
if o.ridged.Compute(ctx) >= 0 {
|
|
return 0, false
|
|
}
|
|
richness := clampedMap(absVeininess, veininessThreshold, maxRichnessThreshold, minRichness, maxRichness)
|
|
if float64(random.NextFloat()) < richness && o.gap.Compute(ctx) > skipOreIfGapNoiseIsBelow {
|
|
if random.NextFloat() < chanceOfRawOreBlock {
|
|
return vein.rawOreBlock, true
|
|
}
|
|
return vein.ore, true
|
|
}
|
|
return vein.filler, true
|
|
}
|