diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a481c66..e26e5b7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -143,9 +143,9 @@ jobs: if-no-files-found: error retention-days: 30 path: | - build/libs/RealisticDeposits-0.6.0.110021.jar - build/libs/RealisticDeposits-0.6.0.110021-sources.jar - build/libs/RealisticDeposits-0.6.0.110021-javadoc.jar + build/libs/RealisticDeposits-0.7.0.110021.jar + build/libs/RealisticDeposits-0.7.0.110021-sources.jar + build/libs/RealisticDeposits-0.7.0.110021-javadoc.jar build/release/SHA256SUMS CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md index f769b32..7f4e641 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # Changelog +## 0.7.0.110021 + +- Keep more ordinary coal, iron and lapis between major finds in fresh worlds. +- Route new diamond starter workings around the pipe wall without changing + the pipe or river placer rewards. +- Search nearby natural marble when a lapis site fails to fit. +- Give rich and large emerald sites a bounded search for suitable positions, + without changing their ore budgets or ordinary emerald generation. +- Keep gold, saved ore rules, existing chunks and saved mine layouts unchanged. + ## 0.6.0.110021 Beta for Minecraft 1.10.2. diff --git a/README.md b/README.md index 47f07a0..237b3f0 100644 --- a/README.md +++ b/README.md @@ -15,9 +15,10 @@ It runs on [MMD OreSpawn](https://github.com/MinecraftModDevelopmentMods/OreSpaw The project starts with Minecraft 1.10.2 and is intended to follow the maintained OreSpawn branches across Forge and NeoForge. -Version 0.6.0.110021 is a local candidate for Minecraft 1.10.2. It adds rare -emerald pockets and occasional abandoned workings without reducing ordinary -emerald generation. Try a fresh world to see the new rules: existing +Version 0.7.0.110021 adjusts the balance for fresh Minecraft 1.10.2 worlds. +Coal, iron and lapis keep more ordinary ore between mines, diamond starter +workings follow the pipe wall, and some lapis and emerald sites can retry a +poor fit. Ordinary emerald generation is unchanged. Existing worlds keep their settings, generated chunks and saved mine layouts. Back up important worlds before updating. @@ -97,12 +98,12 @@ folded horizon, a 160–240 block down-dip span, and a 3–8 block thickness. It `density` controls the disseminated halo around the connected mineralized routes rather than producing a uniformly random cloud. Two or three faults introduce short, recoverable breaks and modest vertical or lateral offsets. -The shipped iron definition uses `hybrid` interaction with 15% background iron +The shipped iron definition uses `hybrid` interaction with 25% background iron so early play remains viable while a discovered district can support a long-lived mine and rail network. The separate coal catalogue adds two to four beds across a large coalfield, -keeping 15% ordinary background coal. It favours sedimentary and wetland basins +keeping 25% ordinary background coal. It favours sedimentary and wetland basins while allowing sedimentary cover in some other geological regions. Coalfields use plain stone without physical strata, or suitable existing sedimentary rocks when those strata are active. Old catalogues are never overwritten, and @@ -119,7 +120,9 @@ Lapis occurs in smaller, irregular lenses rather than huge ore-filled layers. With physical Mineralogy strata, lenses fit into natural marble; limestone, dolomite and decorative marble are not substitutes. Without those strata, natural vanilla stone variants provide a simpler setting. The separate lapis -catalogue keeps 15% background generation. A discovery can supply a lasting +catalogue keeps 30% background generation. Where a proposed site doesn't fit +natural marble, the new defaults check nearby positions in the same region. +A discovery can supply a lasting reserve because each vanilla lapis ore block drops several lapis pieces. Deposits only affect newly generated chunks. Changing these settings does not diff --git a/docs/COALFIELDS.md b/docs/COALFIELDS.md index df4c567..8622b87 100644 --- a/docs/COALFIELDS.md +++ b/docs/COALFIELDS.md @@ -35,7 +35,7 @@ shows every setting. - 32-chunk regions, with a 50% candidate chance before geological selection. - 384-768 blocks along strike and 128-256 blocks across. - Two to four beds, 1-3 blocks thick, separated by 4-12 blocks of rock. -- Hybrid generation with 15% ordinary background coal. +- Hybrid generation with 25% ordinary background coal in fresh defaults. - Abandoned workings attempted at every new coalfield where safe. Existing catalogues and saved-world settings keep their choices. diff --git a/docs/DIAMONDS.md b/docs/DIAMONDS.md index a8817ae..c3814fd 100644 --- a/docs/DIAMONDS.md +++ b/docs/DIAMONDS.md @@ -5,7 +5,7 @@ diamond-bearing river gravel. The 1-in-16 gravel roll stays fixed; pipes make up most of the supply, with river pockets as an occasional bonus. Ordinary background generation stays at 15%. -The six-seed ore survey estimates 3.57 diamonds per chunk against 3.70 in +The original 0.3 six-seed ore survey estimated 3.57 diamonds per chunk against 3.70 in the matched vanilla controls, before Fortune. Calibration and separate holdout averages both fall within 20% of their controls. The estimated split is about 83% pipes, 2% placers and 15% background diamonds. Fortune increases all three @@ -23,6 +23,11 @@ Pipes occur in rare districts, normally beneath OreSpawn's stable cratons. Each district contains one to three irregular bodies, wider near the top and narrower at depth. Diamonds occur in patches rather than filling the whole body. A few can occur close to the surface, but the upper parts are deliberately sparse. +With `shape.rim_workings` enabled in fresh defaults, abandoned galleries follow +the pipe wall and stairs approach from outside. A short inward cut can show +where to dig without opening the rich interior for you. This changes the +workings, not the pipe's ore plan. Older profiles without the setting retain +the original layout. Pipes cut across existing rocks. Without Mineralogy, their hosts are natural vanilla stone, granite, diorite and andesite. With physical Mineralogy strata, diff --git a/docs/EMERALDS.md b/docs/EMERALDS.md index 012a895..8599f84 100644 --- a/docs/EMERALDS.md +++ b/docs/EMERALDS.md @@ -33,10 +33,11 @@ the workings are left for prospecting. The fresh defaults use a 35% selection chance per 64-chunk region before geology and host checks, at Y12–48 and with at least eight blocks of cover. -Eight percent of candidates that reach the layout stage try the larger -layout. If that does not fit the mountain-belt geology and eligible host, -the generator tries the ordinary layout instead. Large mines have a +Fourteen percent of candidates that reach the layout stage try the richer +layout. The fresh defaults try up to five positions or directions before +falling back to the ordinary layout. Large mines have a separate 8% roll, only in regions not selected for small or rich finds. +They may try a second position if the first one doesn't fit. They remain more geographically restricted than iron formations; all their zones must fit the mountain-belt geology and eligible host rock. These are gameplay ranges for individual finds, not estimates of a real mine's production. @@ -51,6 +52,9 @@ the linked galleries, subject to the ordinary abandoned-mine setting. The optional `shape.large_mine` sets the large-mine chance, zone count, strike and ore budget. Large mines always plan their own prospecting clues; the ordinary abandoned-mine setting still controls their starter workings. +Each upper tier's optional `fit_attempts` controls its bounded placement +search. Omit it for the original single attempt. Retries do not raise the ore +budget or relax the host requirement. Leave `shape.large_mine` absent to keep the two-tier layout. Older catalogues without these fields and existing world profiles keep their previous settings. Generated chunks are never rewritten. diff --git a/docs/LAPIS.md b/docs/LAPIS.md index 8f35327..b314c9c 100644 --- a/docs/LAPIS.md +++ b/docs/LAPIS.md @@ -44,7 +44,10 @@ to eight pieces of lapis lazuli, averaging six before Fortune. Fortune increases that yield; Silk Touch returns the ore block instead. No new processing recipes, lapis grades or river placers are added. -The default catalogue retains 15% ordinary background lapis. New lenses are +The fresh default catalogue retains 30% ordinary background lapis. With +physical strata active, `shape.marble_search_radius` lets a failed site try +nearby positions inside the same region. The default radius is 96 blocks; +absent or zero keeps the original single-site search. New lenses are deliberately concentrated, and their reward is not forced to equal vanilla's overall supply. Structures, caves and water can interrupt a deposit or prevent a safe mine entrance. Not every planned lens will be fully recoverable. diff --git a/gradle.properties b/gradle.properties index e68324c..624ff22 100644 --- a/gradle.properties +++ b/gradle.properties @@ -22,7 +22,7 @@ gradle_java_version=17 mod_id=realisticdeposits mod_name=Realistic Deposits mod_license=LGPL-2.1 -mod_version=0.6.0.110021 +mod_version=0.7.0.110021 mod_group=zone.moddev.mc.realisticdeposits mod_authors=SkyBlade1978, the MMD Team mod_description=Rare, massive, and geologically aware ore deposits powered by MMD OreSpawn. diff --git a/gradle/release.gradle b/gradle/release.gradle index b1961ea..c3465e5 100644 --- a/gradle/release.gradle +++ b/gradle/release.gradle @@ -81,7 +81,7 @@ tasks.register('verifyJavadocOrdering') { tasks.register('verifyReleaseConfiguration') { group = 'verification' doLast { - if (project.mod_version != '0.6.0.110021' + if (project.mod_version != '0.7.0.110021' || project.curseforge_project_id != '1689703' || project.mod_artifact != 'RealisticDeposits' || project.java_version != '8' || project.gradle_java_version != '17' diff --git a/src/main/java/zone/moddev/mc/realisticdeposits/RealisticDeposits.java b/src/main/java/zone/moddev/mc/realisticdeposits/RealisticDeposits.java index e0b0029..2affa4a 100644 --- a/src/main/java/zone/moddev/mc/realisticdeposits/RealisticDeposits.java +++ b/src/main/java/zone/moddev/mc/realisticdeposits/RealisticDeposits.java @@ -40,7 +40,7 @@ public final class RealisticDeposits { public static final String MOD_ID = "realisticdeposits"; public static final String NAME = "Realistic Deposits"; - public static final String VERSION = "0.6.0.110021"; + public static final String VERSION = "0.7.0.110021"; private static final OrePatternType STRATIFORM_SEAM = StratiformSeamPattern.type() .setRegistryName(OreSpawnIntegration.PATTERN_ID); private static final OrePatternType COAL_MEASURES = CoalMeasuresPattern.type() diff --git a/src/main/java/zone/moddev/mc/realisticdeposits/config/DepositCatalogLoader.java b/src/main/java/zone/moddev/mc/realisticdeposits/config/DepositCatalogLoader.java index 3419699..87a24c8 100644 --- a/src/main/java/zone/moddev/mc/realisticdeposits/config/DepositCatalogLoader.java +++ b/src/main/java/zone/moddev/mc/realisticdeposits/config/DepositCatalogLoader.java @@ -122,19 +122,21 @@ private static DepositShapeSettings shape(JsonObject shape, String path, List sampler, Set hosts, boolean physical) { return new LapisDistrict.Site() { + @Override public boolean hasPhysicalStrata() { return physical; } + @Override public int surfaceY(int x, int z) { return terrain.naturalSurfaceY(x, z); diff --git a/src/main/java/zone/moddev/mc/realisticdeposits/geometry/AbandonedMinePlanner.java b/src/main/java/zone/moddev/mc/realisticdeposits/geometry/AbandonedMinePlanner.java index 7466fbc..af19a39 100644 --- a/src/main/java/zone/moddev/mc/realisticdeposits/geometry/AbandonedMinePlanner.java +++ b/src/main/java/zone/moddev/mc/realisticdeposits/geometry/AbandonedMinePlanner.java @@ -40,7 +40,7 @@ public static Optional plan(MineableDepositBody body, for (int sign : offset == 0 ? new int[] {1} : new int[] {1, -1}) { int height = initial + offset * sign; if (height < landing.y() + 6 || height - landing.y() > MAXIMUM_SHAFT_DEPTH || height > 245) continue; - MinePoint entrance = MineDecline.plan(landing, height, body.identity().seed()).entrance(); + MinePoint entrance = body.decline(landing, height).entrance(); if (surface.applyAsInt(entrance.x(), entrance.z()) == height) return plan(body, settings, height); } } @@ -112,7 +112,7 @@ public static Optional plan(MineableDepositBody body, if (air.contains(rubble)) blocks.put(rubble, MineBlock.COBBLESTONE); } } - MineDecline access = MineDecline.plan(landing, surfaceY, body.identity().seed()); + MineDecline access = body.decline(landing, surfaceY); access.build(blocks); if (!sealClosedPockets(blocks, access.entrance(), landing)) return Optional.empty(); diff --git a/src/main/java/zone/moddev/mc/realisticdeposits/geometry/DiamondPipeWorkingsBody.java b/src/main/java/zone/moddev/mc/realisticdeposits/geometry/DiamondPipeWorkingsBody.java index 9c3e049..a0eafa8 100644 --- a/src/main/java/zone/moddev/mc/realisticdeposits/geometry/DiamondPipeWorkingsBody.java +++ b/src/main/java/zone/moddev/mc/realisticdeposits/geometry/DiamondPipeWorkingsBody.java @@ -1,6 +1,6 @@ package zone.moddev.mc.realisticdeposits.geometry; -/** A shaft and short crosscuts through one pipe, without changing its ore plan. */ +/** Short starter crosscuts for one pipe, without changing its ore plan. */ public final class DiamondPipeWorkingsBody implements MineableDepositBody { private final DepositIdentity identity; private final DiamondDistrict.Pipe pipe; @@ -10,23 +10,32 @@ public final class DiamondPipeWorkingsBody implements MineableDepositBody { private final double radius; private final double cosine; private final double sine; + private final boolean rim; + private final double bearing; + private final double height; + private final double phase; public DiamondPipeWorkingsBody(DiamondDistrict district, int pipeIndex) { + this(district, pipeIndex, false); + } + + public DiamondPipeWorkingsBody(DiamondDistrict district, int pipeIndex, boolean rim) { pipe = district.pipes().get(pipeIndex); DepositIdentity parent = district.identity(); identity = new DepositIdentity(parent.worldSeed(), parent.dimension(), parent.definition() + "/pipe_" + pipeIndex, parent.regionX(), parent.regionZ(), parent.salt()); level = Math.max(10, pipe.bottomY() + (int) ((pipe.topY() - pipe.bottomY()) * 0.4D)); - double height = (double) (level - pipe.bottomY()) / (pipe.topY() - pipe.bottomY()); - double phase = StableHash.unit(pipe.seed()) * Math.PI * 2; + height = (double) (level - pipe.bottomY()) / (pipe.topY() - pipe.bottomY()); + phase = StableHash.unit(pipe.seed()) * Math.PI * 2; centerX = pipe.centerX() + Math.cos(phase) * pipe.upperRadius() * 0.25D * (1 - height); centerZ = pipe.centerZ() + Math.sin(phase) * pipe.upperRadius() * 0.25D * (1 - height); radius = (3 + (pipe.upperRadius() - 3) * Math.pow(height, 0.75D)) * 0.65D; - double direction = StableHash.unit(identity.seed()) * Math.PI * 2; - cosine = Math.cos(direction); - sine = Math.sin(direction); + this.rim = rim; + bearing = StableHash.unit(identity.seed()) * Math.PI * 2; + cosine = Math.cos(bearing); + sine = Math.sin(bearing); } @Override public DepositIdentity identity() { return identity; } @@ -39,8 +48,33 @@ public DiamondPipeWorkingsBody(DiamondDistrict district, int pipeIndex) { @Override public int maximumCandidateYAt(int x, int z) { return pipe.topY(); } @Override public boolean isOreAt(int x, int y, int z) { return pipe.isOreAt(x, y, z); } + @Override + public MineDecline decline(MinePoint landing, int surfaceY) { + if (!rim) return MineableDepositBody.super.decline(landing, surfaceY); + // Flights run tangentially. Each switchback steps outward so the + // widening pipe doesn't swallow the stairs as they approach the surface. + double x = landing.x() + 0.5 - centerX; + double z = landing.z() + 0.5 - centerZ; + int xDirection = x >= 0 ? 0 : 2; + int zDirection = z >= 0 ? 1 : 3; + boolean dominantX = Math.abs(x) >= Math.abs(z); + return MineDecline.plan(landing, surfaceY, identity.seed(), + dominantX ? zDirection : xDirection, dominantX ? xDirection : zDirection); + } + @Override public MinePoint miningRoutePoint(double along, double lateralOffset) { + if (rim) { + // Follow the irregular wall in country rock; the inward branch only + // grazes the pipe. Its grade, envelope and ore coordinates stay intact. + double angle = bearing + along / 36.0D; + double wall = 1 + 0.10 * Math.sin(angle * 3 + phase + height * 2) + + 0.06 * Math.sin(angle * 7 + phase - height * 4); + double wallRadius = (3 + (pipe.upperRadius() - 3) * Math.pow(height, 0.75D)) * wall; + double distance = wallRadius + 3.0D + lateralOffset * 0.25D; + return new MinePoint((int) Math.floor(centerX + distance * Math.cos(angle)), level, + (int) Math.floor(centerZ + distance * Math.sin(angle))); + } // Keep the galleries inside the wider part of the pipe. The surface // ladder reaches this level; miners can then follow the ore downwards. double forward = along * radius / 36.0D; diff --git a/src/main/java/zone/moddev/mc/realisticdeposits/geometry/EmeraldDistrict.java b/src/main/java/zone/moddev/mc/realisticdeposits/geometry/EmeraldDistrict.java index 084a9e4..4410004 100644 --- a/src/main/java/zone/moddev/mc/realisticdeposits/geometry/EmeraldDistrict.java +++ b/src/main/java/zone/moddev/mc/realisticdeposits/geometry/EmeraldDistrict.java @@ -104,11 +104,20 @@ public static Optional plan(DepositDefinition definition, long double direction = random.nextDouble() * 2 * Math.PI; EmeraldPocketSettings.RichFind rich = settings.rich(); if (rich != null && StableHash.unit(identity.seed() ^ RICH_CHANCE_SALT) < rich.chance()) { - Random richer = new Random(identity.seed() ^ RICH_LAYOUT_SALT); - Optional larger = fitDistrict(definition, identity, site, (int) x, (int) z, - rich.pocketCount().sample(richer), rich.strikeLength().sample(richer), rich.oreBlocks().sample(richer), - cover, targetY, direction, richer, true); - if (larger.isPresent()) return larger; + for (int attempt = 0; attempt < rich.fitAttempts(); attempt++) { + double bearing = direction + attempt * Math.PI / 4; + int px = (int) x, pz = (int) z; + if (attempt >= 2) { + px += (int) Math.round(Math.cos(bearing) * 24); + pz += (int) Math.round(Math.sin(bearing) * 24); + } + if (Math.floorDiv(px, size) != rx || Math.floorDiv(pz, size) != rz) continue; + Random richer = new Random(identity.seed() ^ RICH_LAYOUT_SALT); + Optional larger = fitDistrict(definition, identity, site, px, pz, + rich.pocketCount().sample(richer), rich.strikeLength().sample(richer), rich.oreBlocks().sample(richer), + cover, targetY, bearing, richer, true); + if (larger.isPresent()) return larger; + } } return fitDistrict(definition, identity, site, (int) x, (int) z, count, strike, budget, cover, targetY, direction, random, false); @@ -149,6 +158,15 @@ private static Optional fitDistrict(DepositDefinition definitio private static Optional fitLarge(DepositDefinition definition, DepositIdentity identity, int rx, int rz, EmeraldPocketSettings.LargeMine large, Site site) { + for (int attempt = 0; attempt < large.fitAttempts(); attempt++) { + Optional result = fitLargeAttempt(definition, identity, rx, rz, large, site, attempt); + if (result.isPresent()) return result; + } + return Optional.empty(); + } + + private static Optional fitLargeAttempt(DepositDefinition definition, DepositIdentity identity, + int rx, int rz, EmeraldPocketSettings.LargeMine large, Site site, int attempt) { Random random = new Random(identity.seed() ^ LARGE_LAYOUT_SALT); int strike = large.strikeLength().sample(random), zones = large.zones().sample(random); int budget = large.oreBlocks().sample(random), cover = definition.emerald().surfaceCover().sample(random); @@ -159,11 +177,17 @@ private static Optional fitLarge(DepositDefinition definition, if (size <= 2 * margin) return Optional.empty(); long centerX = (long) rx * size + margin + random.nextInt(size - 2 * margin); long centerZ = (long) rz * size + margin + random.nextInt(size - 2 * margin); + Random alternative = attempt == 0 ? null : new Random(StableHash.combine(identity.seed() ^ LARGE_LAYOUT_SALT, attempt)); + if (alternative != null) { + centerX = (long) rx * size + margin + alternative.nextInt(size - 2 * margin); + centerZ = (long) rz * size + margin + alternative.nextInt(size - 2 * margin); + } if (Math.abs(centerX) > 29_999_800 || Math.abs(centerZ) > 29_999_800 || !site.acceptsGeome((int) centerX, (int) centerZ)) return Optional.empty(); int depth = definition.maximumY() - definition.minimumY() + 1; int targetY = definition.minimumY() + depth / 3 + random.nextInt(Math.max(1, depth / 3)); double direction = random.nextDouble() * 2 * Math.PI; + if (alternative != null) direction = alternative.nextDouble() * 2 * Math.PI; double alongX = Math.cos(direction), alongZ = Math.sin(direction); boolean alongEastWest = Math.abs(alongX) >= Math.abs(alongZ); int stepX = alongEastWest ? (alongX >= 0 ? 4 : -4) : 0; diff --git a/src/main/java/zone/moddev/mc/realisticdeposits/geometry/LapisDistrict.java b/src/main/java/zone/moddev/mc/realisticdeposits/geometry/LapisDistrict.java index 604cc16..503377a 100644 --- a/src/main/java/zone/moddev/mc/realisticdeposits/geometry/LapisDistrict.java +++ b/src/main/java/zone/moddev/mc/realisticdeposits/geometry/LapisDistrict.java @@ -18,6 +18,7 @@ public interface Site { /** Returns a suitable lens centre, or NaN when the host interval is too thin. */ double fitHeight(int x, int z, double targetY, int thickness); + default boolean hasPhysicalStrata() { return false; } } private final DepositIdentity identity; @@ -65,8 +66,35 @@ public static Optional plan(DepositDefinition definition, long se Random random = new Random(identity.seed()); LapisLensSettings settings = definition.lapis(); int size = definition.regionSpacingChunks() * 16; - int x = Math.toIntExact((long) regionX * size + random.nextInt(size)); - int z = Math.toIntExact((long) regionZ * size + random.nextInt(size)); + int originX = Math.toIntExact((long) regionX * size); + int originZ = Math.toIntExact((long) regionZ * size); + int x = Math.toIntExact((long) originX + random.nextInt(size)); + int z = Math.toIntExact((long) originZ + random.nextInt(size)); + Optional original = planAt(definition, identity, settings, site, x, z, random); + if (original.isPresent() || !site.hasPhysicalStrata() || settings.marbleSearchRadius() == 0) return original; + + // Look for nearby natural marble only after the original proposal fails. + // An alternative stays in the same region and uses the same lens settings. + double bearing = StableHash.unit(identity.seed() ^ 0x339F7B6EC5F255DAL) * Math.PI * 2; + for (int ring = 1; ring <= 2; ring++) { + double radius = ring * settings.marbleSearchRadius() / 2.0; + for (int step = 0; step < 8; step++) { + double angle = bearing + step * Math.PI / 4; + int px = x + (int) Math.round(radius * Math.cos(angle)); + int pz = z + (int) Math.round(radius * Math.sin(angle)); + if (Math.floorDiv(px, size) != regionX || Math.floorDiv(pz, size) != regionZ) continue; + Random retry = new Random(identity.seed()); + retry.nextInt(size); + retry.nextInt(size); + Optional fitted = planAt(definition, identity, settings, site, px, pz, retry); + if (fitted.isPresent()) return fitted; + } + } + return Optional.empty(); + } + + private static Optional planAt(DepositDefinition definition, DepositIdentity identity, + LapisLensSettings settings, Site site, int x, int z, Random random) { int surface = site.surfaceY(x, z); int cover = settings.surfaceCover().sample(random); int maximum = Math.min(definition.maximumY(), surface - cover); diff --git a/src/main/java/zone/moddev/mc/realisticdeposits/geometry/MineDecline.java b/src/main/java/zone/moddev/mc/realisticdeposits/geometry/MineDecline.java index fc82828..c920c6f 100644 --- a/src/main/java/zone/moddev/mc/realisticdeposits/geometry/MineDecline.java +++ b/src/main/java/zone/moddev/mc/realisticdeposits/geometry/MineDecline.java @@ -21,6 +21,10 @@ private MineDecline(List steps) { } public static MineDecline plan(MinePoint landing, int surfaceY, long identity) { + return plan(landing, surfaceY, identity, -1, -1); + } + + static MineDecline plan(MinePoint landing, int surfaceY, long identity, int first, int turns) { if (surfaceY <= landing.y() || surfaceY - landing.y() > AbandonedMinePlanner.MAXIMUM_SHAFT_DEPTH) { throw new IllegalArgumentException("Mine access depth is outside the supported range"); } @@ -28,6 +32,10 @@ public static MineDecline plan(MinePoint landing, int surfaceY, long identity) { Random random = new Random(StableHash.mix(identity ^ ACCESS_SALT)); int forward = random.nextInt(4); int across = (forward + (random.nextBoolean() ? 1 : 3)) % 4; + if (first >= 0) { + forward = first; + across = turns; + } int x = landing.x(); int y = landing.y(); int z = landing.z(); diff --git a/src/main/java/zone/moddev/mc/realisticdeposits/geometry/MineableDepositBody.java b/src/main/java/zone/moddev/mc/realisticdeposits/geometry/MineableDepositBody.java index b117b0d..73fc88a 100644 --- a/src/main/java/zone/moddev/mc/realisticdeposits/geometry/MineableDepositBody.java +++ b/src/main/java/zone/moddev/mc/realisticdeposits/geometry/MineableDepositBody.java @@ -12,4 +12,8 @@ public interface MineableDepositBody { int maximumCandidateYAt(int x, int z); boolean isOreAt(int x, int y, int z); MinePoint miningRoutePoint(double along, double lateralOffset); + + default MineDecline decline(MinePoint landing, int surfaceY) { + return MineDecline.plan(landing, surfaceY, identity().seed()); + } } diff --git a/src/main/java/zone/moddev/mc/realisticdeposits/model/DiamondPipeSettings.java b/src/main/java/zone/moddev/mc/realisticdeposits/model/DiamondPipeSettings.java index a5ea2b1..1104053 100644 --- a/src/main/java/zone/moddev/mc/realisticdeposits/model/DiamondPipeSettings.java +++ b/src/main/java/zone/moddev/mc/realisticdeposits/model/DiamondPipeSettings.java @@ -14,9 +14,15 @@ public final class DiamondPipeSettings implements DepositShapeSettings { private final int radius; private final double density; private final double legacyScale; + private final boolean rimWorkings; public DiamondPipeSettings(IntRange pipes, IntRange diameter, IntRange rootY, IntRange cover, int radius, double density, double legacyScale) { + this(pipes, diameter, rootY, cover, radius, density, legacyScale, false); + } + + public DiamondPipeSettings(IntRange pipes, IntRange diameter, IntRange rootY, + IntRange cover, int radius, double density, double legacyScale, boolean rimWorkings) { this.pipes = checked(pipes, 1, 8, "pipe count"); this.diameter = checked(diameter, 8, 256, "upper diameter"); this.rootY = checked(rootY, 1, 128, "root height"); @@ -30,6 +36,7 @@ public DiamondPipeSettings(IntRange pipes, IntRange diameter, IntRange rootY, this.radius = radius; this.density = density; this.legacyScale = legacyScale; + this.rimWorkings = rimWorkings; } @Override public String type() { return "diamond_pipe"; } @@ -40,6 +47,7 @@ public DiamondPipeSettings(IntRange pipes, IntRange diameter, IntRange rootY, public int districtRadius() { return radius; } public double oreDensity() { return density; } public double legacySelectionScale() { return legacyScale; } + public boolean rimWorkings() { return rimWorkings; } @Override public JsonObject toJson() { @@ -52,6 +60,7 @@ public JsonObject toJson() { json.addProperty("district_radius", radius); json.addProperty("ore_density", density); json.addProperty("legacy_selection_scale", legacyScale); + if (rimWorkings) json.addProperty("rim_workings", true); return json; } diff --git a/src/main/java/zone/moddev/mc/realisticdeposits/model/EmeraldPocketSettings.java b/src/main/java/zone/moddev/mc/realisticdeposits/model/EmeraldPocketSettings.java index 5499c90..ff08ec7 100644 --- a/src/main/java/zone/moddev/mc/realisticdeposits/model/EmeraldPocketSettings.java +++ b/src/main/java/zone/moddev/mc/realisticdeposits/model/EmeraldPocketSettings.java @@ -73,12 +73,17 @@ public static final class RichFind { private final IntRange strike; private final IntRange oreBlocks; private final boolean workings; + private final int fitAttempts; public RichFind(double chance, IntRange pockets, IntRange strike, IntRange oreBlocks) { this(chance, pockets, strike, oreBlocks, false); } public RichFind(double chance, IntRange pockets, IntRange strike, IntRange oreBlocks, boolean workings) { + this(chance, pockets, strike, oreBlocks, workings, 1); + } + + public RichFind(double chance, IntRange pockets, IntRange strike, IntRange oreBlocks, boolean workings, int fitAttempts) { if (!Double.isFinite(chance) || chance <= 0 || chance > 1) { throw new IllegalArgumentException("Invalid rich emerald find chance"); } @@ -87,6 +92,7 @@ public RichFind(double chance, IntRange pockets, IntRange strike, IntRange oreBl this.strike = checked(strike, 8, 96, "rich strike length"); this.oreBlocks = checked(oreBlocks, 2, 120, "rich ore budget"); this.workings = workings; + this.fitAttempts = checkedFitAttempts(fitAttempts); validateSpacing(pockets, strike, oreBlocks); } @@ -95,6 +101,7 @@ public RichFind(double chance, IntRange pockets, IntRange strike, IntRange oreBl public IntRange strikeLength() { return strike; } public IntRange oreBlocks() { return oreBlocks; } public boolean workings() { return workings; } + public int fitAttempts() { return fitAttempts; } private JsonObject toJson() { JsonObject json = new JsonObject(); @@ -103,6 +110,7 @@ private JsonObject toJson() { json.add("strike_length", DiamondPipeSettings.array(strike)); json.add("ore_blocks", DiamondPipeSettings.array(oreBlocks)); if (workings) json.addProperty("workings", true); + if (fitAttempts > 1) json.addProperty("fit_attempts", fitAttempts); return json; } } @@ -112,8 +120,13 @@ public static final class LargeMine { private final IntRange zones; private final IntRange strike; private final IntRange oreBlocks; + private final int fitAttempts; public LargeMine(double chance, IntRange zones, IntRange strike, IntRange oreBlocks) { + this(chance, zones, strike, oreBlocks, 1); + } + + public LargeMine(double chance, IntRange zones, IntRange strike, IntRange oreBlocks, int fitAttempts) { if (!Double.isFinite(chance) || chance <= 0 || chance > 1) { throw new IllegalArgumentException("Invalid large emerald mine chance"); } @@ -121,6 +134,7 @@ public LargeMine(double chance, IntRange zones, IntRange strike, IntRange oreBlo this.zones = checked(zones, 3, 6, "large zone count"); this.strike = checked(strike, 96, 192, "large strike length"); this.oreBlocks = checked(oreBlocks, 200, 500, "large ore budget"); + this.fitAttempts = checkedFitAttempts(fitAttempts); if (strike.minimum() < 24 * (zones.maximum() - 1) || oreBlocks.maximum() > 108 * zones.minimum()) { throw new IllegalArgumentException("Large emerald zones need separation and at most 54 ore per pocket"); @@ -131,6 +145,7 @@ public LargeMine(double chance, IntRange zones, IntRange strike, IntRange oreBlo public IntRange zones() { return zones; } public IntRange strikeLength() { return strike; } public IntRange oreBlocks() { return oreBlocks; } + public int fitAttempts() { return fitAttempts; } private JsonObject toJson() { JsonObject json = new JsonObject(); @@ -138,10 +153,16 @@ private JsonObject toJson() { json.add("zones", DiamondPipeSettings.array(zones)); json.add("strike_length", DiamondPipeSettings.array(strike)); json.add("ore_blocks", DiamondPipeSettings.array(oreBlocks)); + if (fitAttempts > 1) json.addProperty("fit_attempts", fitAttempts); return json; } } + private static int checkedFitAttempts(int value) { + if (value < 1 || value > 8) throw new IllegalArgumentException("Emerald fitting requires 1 to 8 attempts"); + return value; + } + private static IntRange checked(IntRange value, int minimum, int maximum, String name) { if (value.minimum() < minimum || value.maximum() > maximum) { throw new IllegalArgumentException("Invalid emerald " + name); diff --git a/src/main/java/zone/moddev/mc/realisticdeposits/model/LapisLensSettings.java b/src/main/java/zone/moddev/mc/realisticdeposits/model/LapisLensSettings.java index bbb1ee5..177698d 100644 --- a/src/main/java/zone/moddev/mc/realisticdeposits/model/LapisLensSettings.java +++ b/src/main/java/zone/moddev/mc/realisticdeposits/model/LapisLensSettings.java @@ -12,9 +12,15 @@ public final class LapisLensSettings implements DepositShapeSettings { private final IntRange thickness; private final IntRange cover; private final double legacyScale; + private final int marbleSearchRadius; public LapisLensSettings(IntRange lenses, IntRange strike, IntRange width, IntRange thickness, IntRange cover, double legacyScale) { + this(lenses, strike, width, thickness, cover, legacyScale, 0); + } + + public LapisLensSettings(IntRange lenses, IntRange strike, IntRange width, + IntRange thickness, IntRange cover, double legacyScale, int marbleSearchRadius) { this.lenses = checked(lenses, 1, 6, "lens count"); this.strike = checked(strike, 32, 256, "strike length"); this.width = checked(width, 6, 48, "width"); @@ -26,6 +32,10 @@ public LapisLensSettings(IntRange lenses, IntRange strike, IntRange width, } this.legacyScale = legacyScale; + if (marbleSearchRadius < 0 || marbleSearchRadius > 128) { + throw new IllegalArgumentException("Lapis marble search radius must be between 0 and 128"); + } + this.marbleSearchRadius = marbleSearchRadius; } @Override @@ -39,6 +49,7 @@ public String type() { public IntRange thickness() { return thickness; } public IntRange surfaceCover() { return cover; } public double legacySelectionScale() { return legacyScale; } + public int marbleSearchRadius() { return marbleSearchRadius; } @Override public JsonObject toJson() { @@ -50,6 +61,7 @@ public JsonObject toJson() { json.add("thickness", DiamondPipeSettings.array(thickness)); json.add("surface_cover", DiamondPipeSettings.array(cover)); json.addProperty("legacy_selection_scale", legacyScale); + if (marbleSearchRadius > 0) json.addProperty("marble_search_radius", marbleSearchRadius); return json; } diff --git a/src/main/resources/assets/realisticdeposits/defaults/coal.json b/src/main/resources/assets/realisticdeposits/defaults/coal.json index 4946762..901b17d 100644 --- a/src/main/resources/assets/realisticdeposits/defaults/coal.json +++ b/src/main/resources/assets/realisticdeposits/defaults/coal.json @@ -8,7 +8,7 @@ "resource": "minecraft:coal_ore", "alignment": "geome_aligned", "interaction": "hybrid", - "background_generation_scale": 0.15, + "background_generation_scale": 0.25, "dimensions": ["minecraft:overworld"], "hosts": ["minecraft:stone", "minecraft:sandstone", "mineralogy:shale", "mineralogy:siltstone", "mineralogy:conglomerate"], "geomes": ["orespawn:sedimentary_basin", "orespawn:wetland_basin", "orespawn:stable_craton", "orespawn:coastal_shelf", "orespawn:mountain_belt"], diff --git a/src/main/resources/assets/realisticdeposits/defaults/diamonds.json b/src/main/resources/assets/realisticdeposits/defaults/diamonds.json index 4402e72..a33f044 100644 --- a/src/main/resources/assets/realisticdeposits/defaults/diamonds.json +++ b/src/main/resources/assets/realisticdeposits/defaults/diamonds.json @@ -26,7 +26,8 @@ "surface_cover": [4, 12], "district_radius": 192, "ore_density": 0.28, - "legacy_selection_scale": 0.25 + "legacy_selection_scale": 0.25, + "rim_workings": true } }, { diff --git a/src/main/resources/assets/realisticdeposits/defaults/emerald.json b/src/main/resources/assets/realisticdeposits/defaults/emerald.json index f337f4a..e7495c2 100644 --- a/src/main/resources/assets/realisticdeposits/defaults/emerald.json +++ b/src/main/resources/assets/realisticdeposits/defaults/emerald.json @@ -25,7 +25,8 @@ "surface_cover": [8, 8], "vein_trace": true, "rich_find": { - "chance": 0.08, + "chance": 0.14, + "fit_attempts": 5, "pockets": [4, 6], "strike_length": [52, 80], "ore_blocks": [70, 120], @@ -33,6 +34,7 @@ }, "large_mine": { "chance": 0.08, + "fit_attempts": 2, "zones": [4, 5], "strike_length": [120, 168], "ore_blocks": [260, 420] diff --git a/src/main/resources/assets/realisticdeposits/defaults/lapis.json b/src/main/resources/assets/realisticdeposits/defaults/lapis.json index a0938e0..ca18ff1 100644 --- a/src/main/resources/assets/realisticdeposits/defaults/lapis.json +++ b/src/main/resources/assets/realisticdeposits/defaults/lapis.json @@ -8,7 +8,7 @@ "resource": "minecraft:lapis_ore", "alignment": "geome_aligned", "interaction": "hybrid", - "background_generation_scale": 0.15, + "background_generation_scale": 0.30, "dimensions": ["minecraft:overworld"], "hosts": ["minecraft:stone"], "geomes": ["orespawn:mountain_belt", "orespawn:stable_craton"], @@ -25,7 +25,8 @@ "width": [12, 24], "thickness": [1, 4], "surface_cover": [4, 12], - "legacy_selection_scale": 0.25 + "legacy_selection_scale": 0.25, + "marble_search_radius": 96 } } ] diff --git a/src/main/resources/assets/realisticdeposits/defaults/vanilla.json b/src/main/resources/assets/realisticdeposits/defaults/vanilla.json index 5e3b950..f9aaf36 100644 --- a/src/main/resources/assets/realisticdeposits/defaults/vanilla.json +++ b/src/main/resources/assets/realisticdeposits/defaults/vanilla.json @@ -8,7 +8,7 @@ "resource": "minecraft:iron_ore", "alignment": "massive_rare", "interaction": "hybrid", - "background_generation_scale": 0.15, + "background_generation_scale": 0.25, "dimensions": [ "minecraft:overworld" ], diff --git a/src/test/java/zone/moddev/mc/realisticdeposits/ProjectIdentityTest.java b/src/test/java/zone/moddev/mc/realisticdeposits/ProjectIdentityTest.java index 670bea9..88564c9 100644 --- a/src/test/java/zone/moddev/mc/realisticdeposits/ProjectIdentityTest.java +++ b/src/test/java/zone/moddev/mc/realisticdeposits/ProjectIdentityTest.java @@ -16,7 +16,7 @@ class ProjectIdentityTest { void usesStablePublicIdentity() { assertEquals("realisticdeposits", RealisticDeposits.MOD_ID); assertEquals("Realistic Deposits", RealisticDeposits.NAME); - assertEquals("0.6.0.110021", RealisticDeposits.VERSION); + assertEquals("0.7.0.110021", RealisticDeposits.VERSION); net.minecraftforge.fml.common.Mod annotation = RealisticDeposits.class.getAnnotation( net.minecraftforge.fml.common.Mod.class); assertTrue(annotation.guiFactory().endsWith("RealisticDepositsGuiFactory")); diff --git a/src/test/java/zone/moddev/mc/realisticdeposits/config/CoalCatalogTest.java b/src/test/java/zone/moddev/mc/realisticdeposits/config/CoalCatalogTest.java index bc39eea..377ad97 100644 --- a/src/test/java/zone/moddev/mc/realisticdeposits/config/CoalCatalogTest.java +++ b/src/test/java/zone/moddev/mc/realisticdeposits/config/CoalCatalogTest.java @@ -24,7 +24,7 @@ void shipsCoalSeparatelyWithoutChangingTheIronCatalogue() { new InputStreamReader(source, StandardCharsets.UTF_8), "coal.json"); assertEquals(1, catalogue.definitions().size()); assertEquals("minecraft:coal_ore", catalogue.definitions().get(0).resource()); - assertEquals(0.15D, catalogue.definitions().get(0).effectiveBackgroundGenerationScale().getAsDouble()); + assertEquals(0.25D, catalogue.definitions().get(0).effectiveBackgroundGenerationScale().getAsDouble()); assertTrue(catalogue.definitions().get(0).isCoal()); assertEquals(32, catalogue.definitions().get(0).regionSpacingChunks()); assertTrue(catalogue.definitions().get(0).abandonedMine().enabled()); diff --git a/src/test/java/zone/moddev/mc/realisticdeposits/config/DepositCatalogLoaderTest.java b/src/test/java/zone/moddev/mc/realisticdeposits/config/DepositCatalogLoaderTest.java index 797ad9b..3f92742 100644 --- a/src/test/java/zone/moddev/mc/realisticdeposits/config/DepositCatalogLoaderTest.java +++ b/src/test/java/zone/moddev/mc/realisticdeposits/config/DepositCatalogLoaderTest.java @@ -32,7 +32,7 @@ void loadsPackagedVanillaIronFormation() { assertEquals("minecraft:iron_ore", catalog.definitions().get(0).resource()); assertEquals(AlignmentMode.MASSIVE_RARE, catalog.definitions().get(0).alignment()); assertEquals(InteractionPolicy.HYBRID, catalog.definitions().get(0).interaction()); - assertEquals(0.15D, catalog.definitions().get(0) + assertEquals(0.25D, catalog.definitions().get(0) .effectiveBackgroundGenerationScale().getAsDouble()); assertEquals(640, catalog.definitions().get(0).seam().strikeLength().minimum()); assertEquals(960, catalog.definitions().get(0).seam().strikeLength().maximum()); diff --git a/src/test/java/zone/moddev/mc/realisticdeposits/config/LapisCatalogTest.java b/src/test/java/zone/moddev/mc/realisticdeposits/config/LapisCatalogTest.java index 822cccb..d10e5ac 100644 --- a/src/test/java/zone/moddev/mc/realisticdeposits/config/LapisCatalogTest.java +++ b/src/test/java/zone/moddev/mc/realisticdeposits/config/LapisCatalogTest.java @@ -33,7 +33,7 @@ void shipsASeparateLapisShapeAndBackgroundController() { assertEquals("lapis_lens", definition.shape().type()); assertEquals("minecraft:lapis_ore", definition.resource()); - assertEquals(0.15, definition.effectiveBackgroundGenerationScale().getAsDouble()); + assertEquals(0.30, definition.effectiveBackgroundGenerationScale().getAsDouble()); assertTrue(definition.abandonedMine().enabled()); assertEquals(1.0, definition.abandonedMine().chance()); } diff --git a/src/test/java/zone/moddev/mc/realisticdeposits/config/RealisticDepositsRuntimeTest.java b/src/test/java/zone/moddev/mc/realisticdeposits/config/RealisticDepositsRuntimeTest.java index c8e3d9b..fe68f23 100644 --- a/src/test/java/zone/moddev/mc/realisticdeposits/config/RealisticDepositsRuntimeTest.java +++ b/src/test/java/zone/moddev/mc/realisticdeposits/config/RealisticDepositsRuntimeTest.java @@ -81,7 +81,7 @@ void installingCoalLeavesBothEditedCataloguesByteForByteAlone() throws IOExcepti try (java.io.InputStream source = getClass().getResourceAsStream( "/assets/realisticdeposits/defaults/coal.json")) { coal = new String(com.google.common.io.ByteStreams.toByteArray(source), StandardCharsets.UTF_8) - .replace("0.15", "0.37").getBytes(StandardCharsets.UTF_8); + .replace("0.25", "0.37").getBytes(StandardCharsets.UTF_8); } Files.write(deposits.resolve("vanilla.json"), iron); Files.write(deposits.resolve("coal.json"), coal); diff --git a/src/test/java/zone/moddev/mc/realisticdeposits/generation/EmeraldDepositTest.java b/src/test/java/zone/moddev/mc/realisticdeposits/generation/EmeraldDepositTest.java index 5b0558d..c1025cd 100644 --- a/src/test/java/zone/moddev/mc/realisticdeposits/generation/EmeraldDepositTest.java +++ b/src/test/java/zone/moddev/mc/realisticdeposits/generation/EmeraldDepositTest.java @@ -107,7 +107,7 @@ void rareFindsExtendTheTrendAndLeaveCapturedSmallFindsAlone() { assertFalse(legacy.toJson().getAsJsonObject("shape").has("rich_find")); assertFalse(legacy.toJson().getAsJsonObject("shape").has("vein_trace")); int total = 0, rich = 0; - for (int rx = -80; rx < 80; rx++) { + for (int rx = -160; rx < 160; rx++) { Optional sample = EmeraldDistrict.plan(definition, 19, OVERWORLD, rx, -1, STONE); if (!sample.isPresent()) continue; total++; @@ -145,7 +145,8 @@ void rareFindsExtendTheTrendAndLeaveCapturedSmallFindsAlone() { } assertTrue(trace.size() >= 5); } - assertTrue(rich > 0 && rich * 4 < total, "Larger finds should be possible but unusual"); + assertTrue(rich > 0 && rich * 4 < total, + "Larger finds should be possible but unusual: " + rich + "/" + total); } @Test diff --git a/src/test/java/zone/moddev/mc/realisticdeposits/generation/MineWorkingsRuntimeProbe.java b/src/test/java/zone/moddev/mc/realisticdeposits/generation/MineWorkingsRuntimeProbe.java index bf7e830..0842685 100644 --- a/src/test/java/zone/moddev/mc/realisticdeposits/generation/MineWorkingsRuntimeProbe.java +++ b/src/test/java/zone/moddev/mc/realisticdeposits/generation/MineWorkingsRuntimeProbe.java @@ -410,8 +410,13 @@ private static void normalWorld(WorldServer world, DepositDefinition definition, } static void verifyAccess(WorldServer world, AbandonedMinePlan plan, boolean hasMarker) { + verifyAccess(world, plan, hasMarker, null); + } + + static void verifyAccess(WorldServer world, AbandonedMinePlan plan, boolean hasMarker, MineDecline expectedDecline) { if (!plan.blocks().containsValue(MineBlock.LADDER_WEST)) { - List steps = MineDecline.plan(plan.landing(), plan.entrance().y(), plan.identity()).steps(); + List steps = (expectedDecline == null + ? MineDecline.plan(plan.landing(), plan.entrance().y(), plan.identity()) : expectedDecline).steps(); require(steps.get(steps.size() - 1).point().equals(plan.entrance()), "The access entrance moved"); int loaded = world.getChunkProvider().getLoadedChunkCount(); diff --git a/src/test/java/zone/moddev/mc/realisticdeposits/generation/PipeWorkingsRuntimeChecks.java b/src/test/java/zone/moddev/mc/realisticdeposits/generation/PipeWorkingsRuntimeChecks.java index 9998db6..adb64c1 100644 --- a/src/test/java/zone/moddev/mc/realisticdeposits/generation/PipeWorkingsRuntimeChecks.java +++ b/src/test/java/zone/moddev/mc/realisticdeposits/generation/PipeWorkingsRuntimeChecks.java @@ -45,6 +45,7 @@ static void run(WorldServer world, String phase) throws Exception { String scenario = System.getProperty("rd.mines.scenario", "flat"); boolean obstructed = "blocked".equals(scenario); boolean undergroundWater = "mineralogy".equals(scenario); + boolean rimLava = "normal-structures".equals(scenario) && geometry.pipe().rimWorkings(); if (scenario.startsWith("mineralogy")) { require(CoalGeology.active().physicalStrata, "The Mineralogy fixture did not activate physical strata"); } @@ -63,7 +64,8 @@ static void run(WorldServer world, String phase) throws Exception { if (!district.isPresent()) continue; for (int index = 0; index < district.get().pipes().size(); index++) { - DiamondPipeWorkingsBody candidate = new DiamondPipeWorkingsBody(district.get(), index); + DiamondPipeWorkingsBody candidate = new DiamondPipeWorkingsBody( + district.get(), index, geometry.pipe().rimWorkings()); int surface = "wet".equals(scenario) ? 129 : terrain.surfaceY(AbandonedMinePlanner.landing(candidate)); Optional proposed = "wet".equals(scenario) ? AbandonedMinePlanner.plan(candidate, new AbandonedMineSettings(true, 1.0D), surface) @@ -97,7 +99,8 @@ static void run(WorldServer world, String phase) throws Exception { DiamondDistrict district = district(world, geometry, terrain, sampler, Integer.parseInt(target.getProperty("region_x")), Integer.parseInt(target.getProperty("region_z"))).get(); - body = new DiamondPipeWorkingsBody(district, Integer.parseInt(target.getProperty("pipe_index"))); + body = new DiamondPipeWorkingsBody(district, + Integer.parseInt(target.getProperty("pipe_index")), geometry.pipe().rimWorkings()); int surface = "wet".equals(scenario) ? 129 : terrain.surfaceY(AbandonedMinePlanner.landing(body)); plan = "wet".equals(scenario) ? AbandonedMinePlanner.plan(body, new AbandonedMineSettings(true, 1.0D), surface).get() : AbandonedMinePlanner.plan(body, new AbandonedMineSettings(true, 1.0D), terrain::naturalSurfaceY).get(); @@ -144,7 +147,7 @@ static void run(WorldServer world, String phase) throws Exception { if (!rejected) { require(data.plan(key) != null, "Pipe mine rejected: " + data.rejectionReason(key)); require(plan.blocks().equals(data.plan(key).blocks()), "Native pipe mine differs from its planner"); - if (!obstructed && !undergroundWater) { + if (!obstructed && !undergroundWater && !rimLava) { verify(world, plan, populatedChunks(world, plan), false); world.setBlockState(marker(plan), Blocks.DIAMOND_BLOCK.getDefaultState(), 2); } @@ -183,22 +186,29 @@ static void run(WorldServer world, String phase) throws Exception { require(world.getBlockState(chestPosition.down(2)).getBlock() != Blocks.PLANKS, "The unsafe section was partly excavated"); report.setProperty("obstructed_section_preserved", "true"); - } else if (undergroundWater) { - // This native-strata site has a spring beside the decline. The - // safety check must leave that whole section sealed, not drain it. - BlockPos spring = new BlockPos(49495, 36, 56632); - require(world.getBlockState(spring).getMaterial().isLiquid(), "The protected underground spring changed"); - for (AbandonedMinePlan.Cell cell : plan.cellsInChunk(spring.getX() >> 4, spring.getZ() >> 4)) { + } else if (undergroundWater || rimLava) { + // The fixed rim route in normal terrain intersects a lava pocket; + // the legacy physical-strata fixture instead crosses a spring. + // Neither must be opened or partially excavated. + BlockPos liquid = rimLava ? new BlockPos(49498, 27, 49694) : new BlockPos(49499, 24, 56655); + require(world.getBlockState(liquid).getMaterial() == (rimLava + ? net.minecraft.block.material.Material.LAVA : net.minecraft.block.material.Material.WATER), + "The protected underground liquid changed"); + List blocked = plan.cellsInChunk(liquid.getX() >> 4, liquid.getZ() >> 4); + require(!blocked.isEmpty(), "The fixed liquid no longer tests a planned section"); + for (AbandonedMinePlan.Cell cell : blocked) { if (cell.block() == zone.moddev.mc.realisticdeposits.geometry.MineBlock.AIR) continue; MinePoint point = cell.point(); require(!world.getBlockState(new BlockPos(point.x(), point.y(), point.z())).equals(expected(cell.block())), "The flooded section was partly excavated at " + point); } - report.setProperty("underground_water_preserved", "true"); + report.setProperty(rimLava ? "underground_lava_preserved" : "underground_water_preserved", "true"); } else { List built = populatedChunks(world, plan); report.setProperty("checked_cells", Integer.toString(verify(world, plan, built, true))); - if (built.size() == allChunks(plan).size()) verifyAccess(world, plan, true); + if (built.size() == allChunks(plan).size()) { + verifyAccess(world, plan, true, body.decline(plan.landing(), plan.entrance().y())); + } } } diff --git a/src/test/java/zone/moddev/mc/realisticdeposits/geometry/DiamondPipeWorkingsTest.java b/src/test/java/zone/moddev/mc/realisticdeposits/geometry/DiamondPipeWorkingsTest.java index 4743214..4818ed4 100644 --- a/src/test/java/zone/moddev/mc/realisticdeposits/geometry/DiamondPipeWorkingsTest.java +++ b/src/test/java/zone/moddev/mc/realisticdeposits/geometry/DiamondPipeWorkingsTest.java @@ -93,4 +93,41 @@ private static Set reachable(AbandonedMinePlan plan) { return visited; } + + @Test + void rimRouteKeepsThePipeButExposesLessOfItsOre() { + int checked = 0, legacyOre = 0, rimOre = 0; + for (int region = -8; region < 8; region++) { + java.util.Optional picked = DiamondDistrict.plan(DiamondFixtures.pipe(), + 3346071746553592696L, "minecraft:overworld", region, -3, SITE); + if (!picked.isPresent()) continue; + DiamondDistrict district = picked.get(); + for (int i = 0; i < district.pipes().size(); i++) { + DiamondPipeWorkingsBody legacy = new DiamondPipeWorkingsBody(district, i); + DiamondPipeWorkingsBody rim = new DiamondPipeWorkingsBody(district, i, true); + AbandonedMinePlan a = AbandonedMinePlanner.plan(legacy, ALWAYS, 80).get(); + AbandonedMinePlan b = AbandonedMinePlanner.plan(rim, ALWAYS, 80).get(); + assertEquals(legacy.identity().toString(), rim.identity().toString()); + assertEquals(b.blocks(), AbandonedMinePlanner.plan(rim, ALWAYS, 80).get().blocks()); + assertTrue(reachable(b).contains(b.landing().packed())); + legacyOre += exposed(a, legacy); + rimOre += exposed(b, rim); + checked++; + } + } + assertTrue(checked >= 8 && rimOre > 0 && rimOre * 2 < legacyOre, + "Rim routes should offer a clue without exposing the core: " + rimOre + " vs " + legacyOre); + } + + private static int exposed(AbandonedMinePlan plan, DiamondPipeWorkingsBody body) { + Set ore = new HashSet<>(); + for (long packed : reachable(plan)) { + MinePoint p = MinePoint.unpack(packed); + for (int[] d : new int[][] {{1,0,0}, {-1,0,0}, {0,1,0}, {0,-1,0}, {0,0,1}, {0,0,-1}}) { + MinePoint q = new MinePoint(p.x()+d[0], p.y()+d[1], p.z()+d[2]); + if (!plan.blocks().containsKey(q.packed()) && body.isOreAt(q.x(),q.y(),q.z())) ore.add(q.packed()); + } + } + return ore.size(); + } } diff --git a/src/test/java/zone/moddev/mc/realisticdeposits/geometry/LapisDistrictTest.java b/src/test/java/zone/moddev/mc/realisticdeposits/geometry/LapisDistrictTest.java index a6974b0..8805437 100644 --- a/src/test/java/zone/moddev/mc/realisticdeposits/geometry/LapisDistrictTest.java +++ b/src/test/java/zone/moddev/mc/realisticdeposits/geometry/LapisDistrictTest.java @@ -69,6 +69,35 @@ void lensesStayIdenticalAcrossChunkOrdersAndFreshPlanners() { assertEquals(blocks(first, false), blocks(body(81), true)); } + @Test + void boundedMarbleSearchRecoversAFailedSiteWithoutChangingLegacyOrNativeGeometry() { + long seed = 81; + LapisDistrict original = body(seed); + int rx = original.identity().regionX(), rz = original.identity().regionZ(); + DepositDefinition current = LapisCatalogTest.defaults(); + com.google.gson.JsonObject json = current.toJson(); + json.getAsJsonObject("shape").remove("marble_search_radius"); + DepositDefinition captured = zone.moddev.mc.realisticdeposits.config.DepositCatalogLoader + .loadDefinition(json, "captured lapis"); + assertEquals(json, captured.toJson()); + LapisDistrict.Site marble = new LapisDistrict.Site() { + @Override public int surfaceY(int x, int z) { return 90; } + @Override public String geome(int x, int z, int y) { return "orespawn:mountain_belt"; } + @Override public boolean hasPhysicalStrata() { return true; } + @Override public double fitHeight(int x, int z, double target, int thickness) { + return Math.hypot(x - original.centerX(), z - original.centerZ()) < 37 ? Double.NaN : target; + } + }; + assertFalse(LapisDistrict.plan(captured, seed, "minecraft:overworld", rx, rz, marble).isPresent()); + LapisDistrict fitted = LapisDistrict.plan(current, seed, "minecraft:overworld", rx, rz, marble).get(); + assertEquals(original.identity().toString(), fitted.identity().toString()); + assertEquals(rx, Math.floorDiv(fitted.centerX(), 1024)); + assertEquals(rz, Math.floorDiv(fitted.centerZ(), 1024)); + assertEquals(blocks(fitted, false), blocks(LapisDistrict.plan(current, seed, "minecraft:overworld", rx, rz, marble).get(), true)); + assertEquals(blocks(original, false), blocks(LapisDistrict.plan(captured, seed, "minecraft:overworld", rx, rz, + site("orespawn:mountain_belt", 90)).get(), true)); + } + @Test void richPocketsContainFollowableSectionsRatherThanScatteredSingleBlocks() { LapisDistrict body = body(81); diff --git a/tools/qualification/emerald_upgrade_110.py b/tools/qualification/emerald_upgrade_110.py index 689914f..812fc05 100644 --- a/tools/qualification/emerald_upgrade_110.py +++ b/tools/qualification/emerald_upgrade_110.py @@ -1,4 +1,4 @@ -"""Read a Lapis mine on matched disposable 0.5 and 0.6 world copies.""" +"""Read a Lapis mine on matched disposable 0.5 and current-candidate world copies.""" import argparse import json @@ -74,7 +74,7 @@ def main(): if (old.get("owner") == "orespawn" and "owner_version" in old and new.get("owner_version") == NEW_ENGINE_VERSION): new["owner_version"] = old["owner_version"] - check_saved_profile(before, after, "0.6.0.110021") + check_saved_profile(before, after, "0.7.0.110021") else: assert before == after assert not any(key.endswith("overworld_emerald_pockets") for key in after["ores"]) diff --git a/tools/qualification/pipe_mines_110.py b/tools/qualification/pipe_mines_110.py index 12a0d9c..353b7af 100644 --- a/tools/qualification/pipe_mines_110.py +++ b/tools/qualification/pipe_mines_110.py @@ -58,6 +58,12 @@ def main(): location = ROOT / (f"run/config/realisticdeposits/deposits/{name}.json" if case == "regular" else f"src/main/resources/assets/realisticdeposits/defaults/{name}.json") catalogue = json.loads(location.read_text()) + if name == "diamonds" and case == "mineralogy": + # This saved regression targets the legacy decline beside a + # fixed spring. Fresh rim layouts have separate terrain tests. + for definition in catalogue["deposits"]: + if definition["shape"]["type"] == "diamond_pipe": + definition["shape"].pop("rim_workings", None) if not natural: for definition in catalogue["deposits"]: definition["enabled"] = definition["shape"]["type"] == "diamond_pipe" and case != "source-off" diff --git a/tools/release/test_release.py b/tools/release/test_release.py index 2c453f3..cebcf97 100644 --- a/tools/release/test_release.py +++ b/tools/release/test_release.py @@ -13,7 +13,7 @@ def test_public_dependency_and_candidate_version(self): properties = dict(line.split("=", 1) for line in (ROOT / "gradle.properties").read_text().splitlines() if "=" in line and not line.startswith("#")) - self.assertEqual("0.6.0.110021", properties["mod_version"]) + self.assertEqual("0.7.0.110021", properties["mod_version"]) self.assertEqual("4.1.6.110021", properties["orespawn_version"]) self.assertEqual("9518063", properties["orespawn_curse_file_id"]) self.assertEqual("05DB7CBFAADC815D6A713DACDFB9F0647BCED49CCFC357FB2FF010E95BD1379F",