Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -126,9 +126,9 @@ jobs:
if-no-files-found: error
retention-days: 30
path: |
build/libs/OreSpawn-4.1.3.110021.jar
build/libs/OreSpawn-4.1.3.110021-sources.jar
build/libs/OreSpawn-4.1.3.110021-javadoc.jar
build/libs/OreSpawn-4.1.4.110021.jar
build/libs/OreSpawn-4.1.4.110021-sources.jar
build/libs/OreSpawn-4.1.4.110021-javadoc.jar
build/release/SHA256SUMS
CHANGELOG.txt

Expand Down
6 changes: 6 additions & 0 deletions CHANGELOG.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
Version 4.1.4.110021

* Let Java providers select an exact legacy host variant, such as plain stone
without granite. Keep existing block selectors and placement behaviour.
* Reject invalid exact-host weights without leaving a partly configured host.

Version 4.1.3.110021

* Apply hybrid and replacement background limits to imported built-in ore
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ It gives mods and modpacks one place to configure ores, deposit shapes, optional
rock strata and geomes, provider-owned underground fluid deposits, biome
palettes and world materials, flat bedrock, and bounded ore retrogen.

This branch builds target-qualified version `4.1.3.110021`: the OreSpawn 4.1.3
This branch builds target-qualified version `4.1.4.110021`: the OreSpawn 4.1.4
feature set for Minecraft 1.10.2 and Forge. It adds stable generation identity,
non-loading geology sampling, per-resource background control, existing-world
provider merge policy, an Ore Sources arbiter for equivalent provider ores and
Expand Down
14 changes: 14 additions & 0 deletions docs/API.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,19 @@
# Java API

## Exact legacy host states

Since 4.1.4.110021, Java providers can use `hostState(blockId, metadata)` on the
ore dimension builder. For example, `hostState(new ResourceLocation("minecraft",
"stone"), 0)` accepts plain stone without also accepting granite, diorite or
andesite. A third argument supplies a replacement weight between zero and one.

`hostBlock` still selects the whole block, as before. Families and other hosts
are alternatives, so do not add a broad stone or family selector when the
restriction needs to be exact. This addition uses the existing JSON metadata
matcher and does not change old providers. Mods that call the new method must
require OreSpawn 4.1.4 or later; older providers keep their existing dependency
range.

This branch targets Minecraft 1.10.2 and Forge 12. Public examples therefore
use the public `ResourceLocation(String, String)` constructor. Forge 12 has no
`DeferredRegister`, so provider mods use OreSpawn's API-major-1
Expand Down
2 changes: 1 addition & 1 deletion docs/VERSIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ Examples:

| Minecraft | Loader | Target | Example full version |
| --- | --- | ---: | --- |
| 1.10.2 | Forge | `110021` | `4.1.3.110021` |
| 1.10.2 | Forge | `110021` | `4.1.4.110021` |
| 1.12.2 | Forge | `112021` | `4.0.6.112021` |
| 1.13.2 | Forge | `113021` | `4.0.6.113021` |
| 1.20.6 | Forge | `120061` | `4.0.6.120061` |
Expand Down
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ mod_vendor=Minecraft Mod Development
mod_license=LGPL-2.1
mod_license_name=GNU Lesser General Public License, Version 2.1
mod_license_url=https://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt
mod_version=4.1.3.110021
mod_version=4.1.4.110021
mod_group=zone.moddev.mc.orespawn
mod_authors=SkyBlade1978, dshadowwolf, the MMD Team
mod_description=Configurable, provider-driven terrain, ore, and deposit generation.
Expand Down
6 changes: 3 additions & 3 deletions gradle/release/artifacts.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ tasks.register('verifyReleaseConfiguration') {
description = 'Validates the target-qualified release, API, schemas, reports, and publishing identity.'

doLast {
if (project.mod_version != '4.1.3.110021'
if (project.mod_version != '4.1.4.110021'
|| project.mod_group != expectedMavenGroup) {
throw new GradleException("Unexpected OreSpawn release version: ${project.mod_version}")
}
Expand Down Expand Up @@ -106,8 +106,8 @@ tasks.register('verifyReleaseConfiguration') {
'src/main/java/zone/moddev/mc/orespawn/worldgen/LegacyOs3ProfileMigration.java',
'README.md', 'CHANGELOG.txt'
].each { path ->
if (!file(path).getText('UTF-8').contains('4.1.3.110021')) {
throw new GradleException("Authoritative release location does not contain 4.1.3.110021: ${path}")
if (!file(path).getText('UTF-8').contains('4.1.4.110021')) {
throw new GradleException("Authoritative release location does not contain 4.1.4.110021: ${path}")
}
}
if (!file('docs/API.md').getText('UTF-8').contains('orespawn@[4.0.6,5.0.0)')) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1027,7 +1027,7 @@ private static void writeHumanUpgradeReport(Path destination) throws IOException
}
}
List<String> lines = new ArrayList<>();
lines.add("OreSpawn 4.1.3.110021 Upgrade Report");
lines.add("OreSpawn 4.1.4.110021 Upgrade Report");
lines.add("================================");
lines.add("");
lines.add("RESULT: Legacy OreSpawn configuration was consumed and translated for OS4.");
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/cyano/orespawn/OreSpawn.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public class OreSpawn {
public static OreSpawn INSTANCE;
public static final String MODID = "orespawn";
public static final String NAME = "OreSpawn";
public static final String VERSION = "4.1.3.110021";
public static final String VERSION = "4.1.4.110021";
public static final List<Path> oreSpawnConfigFiles = new ArrayList<>();
public static final List<String> additionalStoneBlocks = new ArrayList<>();
public static boolean disableVanillaOreGen;
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/zone/moddev/mc/orespawn/OreSpawn.java
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ public class OreSpawn {

public static final String MODID = "orespawn";
public static final String NAME = "OreSpawn";
public static final String VERSION = "4.1.3.110021";
public static final String VERSION = "4.1.4.110021";

private static final Logger LOGGER = LogManager.getLogger();

Expand Down
39 changes: 37 additions & 2 deletions src/main/java/zone/moddev/mc/orespawn/api/WorldgenProvider.java
Original file line number Diff line number Diff line change
Expand Up @@ -566,6 +566,7 @@ public static final class OreDimensionDefinition implements JsonDefinition {
private final Set<GeologyFamily> hostFamilies;
private final Map<ResourceLocation, Double> geomes;
private final Set<ResourceLocation> hostBlocks;
private final Map<ResourceLocation, Map<Integer, Double>> hostStates;
private final Set<ResourceLocation> hostTags;
private final Set<ResourceLocation> biomeIds;
private final Set<ResourceLocation> excludedBiomeIds;
Expand Down Expand Up @@ -597,6 +598,9 @@ private OreDimensionDefinition(Builder builder) {
hostFamilies = Collections.unmodifiableSet(new LinkedHashSet<>(builder.hostFamilies));
geomes = immutableMap(builder.geomes);
hostBlocks = immutableSet(builder.hostBlocks);
Map<ResourceLocation, Map<Integer, Double>> states = new LinkedHashMap<>();
builder.hostStates.forEach((block, variants) -> states.put(block, immutableMap(variants)));
hostStates = immutableMap(states);
hostTags = immutableSet(builder.hostTags);
biomeIds = immutableSet(builder.biomeIds);
excludedBiomeIds = immutableSet(builder.excludedBiomeIds);
Expand Down Expand Up @@ -631,6 +635,8 @@ private OreDimensionDefinition(Builder builder) {
public Set<GeologyFamily> hostFamilies() { return hostFamilies; }
public Map<ResourceLocation, Double> geomes() { return geomes; }
public Set<ResourceLocation> hostBlocks() { return hostBlocks; }
/** Exact legacy variants, separate from whole-block selectors. */
public Map<ResourceLocation, Map<Integer, Double>> hostStates() { return hostStates; }
public Set<ResourceLocation> hostTags() { return hostTags; }
public Set<ResourceLocation> biomeIds() { return biomeIds; }
public Set<ResourceLocation> excludedBiomeIds() { return excludedBiomeIds; }
Expand Down Expand Up @@ -679,7 +685,15 @@ public JsonObject toJson() {
for (GeologyFamily family : hostFamilies) { families.add(new JsonPrimitive(family.configName())); }
json.add("host_families", families);
json.add("geomes", weights(geomes));
json.add("host_blocks", weightedIds(hostBlocks, hostBlockWeights, "block"));
JsonArray hosts = weightedIds(hostBlocks, hostBlockWeights, "block");
hostStates.forEach((block, variants) -> variants.forEach((metadata, weight) -> {
JsonObject state = new JsonObject();
state.addProperty("block", block.toString());
state.addProperty("metadata", metadata);
if (weight != 1.0D) state.addProperty("weight", weight);
hosts.add(state);
}));
json.add("host_blocks", hosts);
json.add("host_tags", weightedIds(hostTags, hostTagWeights, "tag"));
json.add("biome_ids", ids(biomeIds));
json.add("excluded_biome_ids", ids(excludedBiomeIds));
Expand Down Expand Up @@ -711,6 +725,7 @@ public static final class Builder {
private final Set<GeologyFamily> hostFamilies = new LinkedHashSet<>();
private final Map<ResourceLocation, Double> geomes = new LinkedHashMap<>();
private final Set<ResourceLocation> hostBlocks = new LinkedHashSet<>();
private final Map<ResourceLocation, Map<Integer, Double>> hostStates = new LinkedHashMap<>();
private final Set<ResourceLocation> hostTags = new LinkedHashSet<>();
private final Set<ResourceLocation> biomeIds = new LinkedHashSet<>();
private final Set<ResourceLocation> excludedBiomeIds = new LinkedHashSet<>();
Expand Down Expand Up @@ -751,6 +766,25 @@ public Builder placementChannel(ResourceLocation value) {
public Builder hostFamily(GeologyFamily value) { hostFamilies.add(value); return this; }
public Builder geomeWeight(ResourceLocation geome, double value) { geomes.put(geome, value); return this; }
public Builder hostBlock(ResourceLocation value) { hostBlocks.add(value); return this; }

/** Selects one legacy block variant, such as plain stone (metadata zero). */
public Builder hostState(ResourceLocation block, int metadata) {
return hostState(block, metadata, 1.0D);
}

/** Adds an exact variant with the same replacement weight as hostBlock. */
public Builder hostState(ResourceLocation block, int metadata, double weight) {
Objects.requireNonNull(block, "block");
if (metadata < 0 || metadata > 15) {
throw new IllegalArgumentException("Legacy host metadata must be within 0..15");
}

double validatedWeight = replacementWeight(weight);
hostStates.computeIfAbsent(block, ignored -> new LinkedHashMap<>())
.put(metadata, validatedWeight);
return this;
}

public Builder hostTag(ResourceLocation value) { hostTags.add(value); return this; }
public Builder biome(ResourceLocation value) { biomeIds.add(value); return this; }
public Builder excludeBiome(ResourceLocation value) { excludedBiomeIds.add(value); return this; }
Expand Down Expand Up @@ -791,7 +825,8 @@ public OreDimensionDefinition build() {
|| nodeSize < 1 || nodeSize > 32) {
throw new IllegalStateException("Invalid ore placement values for " + dimension);
}
if (enabled && hostFamilies.isEmpty() && hostBlocks.isEmpty() && hostTags.isEmpty()) {
if (enabled && hostFamilies.isEmpty() && hostBlocks.isEmpty()
&& hostStates.isEmpty() && hostTags.isEmpty()) {
throw new IllegalStateException("Enabled ore dimension has no hosts: " + dimension);
}
return new OreDimensionDefinition(this);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ private static void writeUpgradeReport(Path worldRoot, Path configDirectory,
}

List<String> lines = new ArrayList<>();
lines.add("OreSpawn 4.1.3.110021 Upgrade Report");
lines.add("OreSpawn 4.1.4.110021 Upgrade Report");
lines.add("================================");
lines.add("");
lines.add("RESULT: Existing Mineralogy " + mineralogyVersion
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ private static void writeInitialUpgradeReport(Path configDirectory,
boolean forceRetrogen, boolean flatBedrock, boolean retrogenBedrock,
int bedrockLayers) throws IOException {
String newline = System.lineSeparator();
String text = "OreSpawn 4.1.3.110021 Upgrade Report" + newline
String text = "OreSpawn 4.1.4.110021 Upgrade Report" + newline
+ "================================" + newline + newline
+ "RESULT: Legacy OreSpawn settings were imported into the OS4 profile." + newline
+ "- Manage vanilla ores: " + manageVanilla + newline
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package zone.moddev.mc.orespawn.api;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertAll;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;

import java.lang.reflect.Method;

import org.junit.jupiter.api.Test;

import net.minecraft.util.ResourceLocation;

class ExactHostStateApiTest {

@Test
void publicBuilderCanSelectPlainStoneWithoutItsIgneousVariants() throws Exception {
WorldgenProvider.OreDimensionDefinition.Builder builder =
WorldgenProvider.OreDimensionDefinition.builder(new ResourceLocation("minecraft:overworld"));
Method hostState = builder.getClass().getMethod("hostState", ResourceLocation.class, int.class);
hostState.invoke(builder, new ResourceLocation("minecraft:stone"), 0);

assertEquals("[{\"block\":\"minecraft:stone\",\"metadata\":0}]",
builder.build().toJson().getAsJsonArray("host_blocks").toString());
}

@Test
void exactHostsAreImmutableWeightedAndDoNotChangeOldBlockSelectors() {
ResourceLocation stone = new ResourceLocation("minecraft:stone");
WorldgenProvider.OreDimensionDefinition.Builder builder =
WorldgenProvider.OreDimensionDefinition.builder(new ResourceLocation("minecraft:overworld"));
String original = builder.hostBlock(stone).build().toJson().toString();
WorldgenProvider.OreDimensionDefinition exact = builder.hostState(stone, 0, 0.5D).build();

assertEquals(0.5D, exact.hostStates().get(stone).get(0));
assertThrows(UnsupportedOperationException.class, () -> exact.hostStates().clear());
assertThrows(UnsupportedOperationException.class, () -> exact.hostStates().get(stone).put(1, 1.0D));
builder.hostState(stone, 1);
assertEquals(1, exact.hostStates().get(stone).size());
assertEquals(original, WorldgenProvider.OreDimensionDefinition
.builder(new ResourceLocation("minecraft:overworld")).hostBlock(stone).build().toJson().toString());
assertThrows(IllegalArgumentException.class, () -> builder.hostState(stone, -1));
assertThrows(IllegalArgumentException.class, () -> builder.hostState(stone, 16));
assertThrows(NullPointerException.class, () -> builder.hostState(null, 0));
}

@Test
void rejectedWeightsDoNotBypassTheNoHostCheck() {
assertAll("invalid weights", () -> rejectWithoutAddingHost(-0.1D),
() -> rejectWithoutAddingHost(1.1D), () -> rejectWithoutAddingHost(Double.NaN),
() -> rejectWithoutAddingHost(Double.POSITIVE_INFINITY),
() -> rejectWithoutAddingHost(Double.NEGATIVE_INFINITY));
}

private static void rejectWithoutAddingHost(double weight) {
WorldgenProvider.OreDimensionDefinition.Builder builder =
WorldgenProvider.OreDimensionDefinition.builder(new ResourceLocation("minecraft:overworld"));
assertThrows(IllegalArgumentException.class,
() -> builder.hostState(new ResourceLocation("minecraft:stone"), 0, weight));
assertThrows(IllegalStateException.class, builder::build,
"A rejected host must not make an empty rule valid: " + weight);
}

@Test
void rejectedWeightsLeaveExistingHostsUnchanged() {
ResourceLocation stone = new ResourceLocation("minecraft:stone");
ResourceLocation dirt = new ResourceLocation("minecraft:dirt");
WorldgenProvider.OreDimensionDefinition.Builder builder =
WorldgenProvider.OreDimensionDefinition.builder(new ResourceLocation("minecraft:overworld"))
.hostState(stone, 0, 0.5D);
String original = builder.build().toJson().toString();
assertThrows(IllegalArgumentException.class, () -> builder.hostState(dirt, 0, Double.NaN));
assertThrows(IllegalArgumentException.class, () -> builder.hostState(stone, 0, 2.0D));
WorldgenProvider.OreDimensionDefinition result = builder.build();
assertFalse(result.hostStates().containsKey(dirt));
assertEquals(0.5D, result.hostStates().get(stone).get(0));
assertEquals(original, result.toJson().toString());
}

@Test
void endpointsAndSeveralVariantsSerializeInInsertionOrder() {
WorldgenProvider.OreDimensionDefinition value =
WorldgenProvider.OreDimensionDefinition.builder(new ResourceLocation("minecraft:overworld"))
.hostState(new ResourceLocation("minecraft:stone"), 0, 0.0D)
.hostState(new ResourceLocation("minecraft:stone"), 1, 0.5D)
.hostState(new ResourceLocation("minecraft:stone"), 6, 1.0D).build();
assertEquals("[{\"block\":\"minecraft:stone\",\"metadata\":0,\"weight\":0.0},"
+ "{\"block\":\"minecraft:stone\",\"metadata\":1,\"weight\":0.5},"
+ "{\"block\":\"minecraft:stone\",\"metadata\":6}]",
value.toJson().getAsJsonArray("host_blocks").toString());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package zone.moddev.mc.orespawn.worldgen;

import static org.junit.jupiter.api.Assertions.*;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.Random;

import com.google.gson.JsonObject;

import org.junit.jupiter.api.Test;

import net.minecraft.block.state.IBlockState;
import net.minecraft.init.Blocks;
import net.minecraft.util.ResourceLocation;

import zone.moddev.mc.orespawn.api.WorldgenProvider;
import zone.moddev.mc.orespawn.test.Forge12TestBootstrap;

class ExactHostStateBakeTest {
@Test
void providerStatesReachTheExistingMatcherWithoutAcceptingSiblingVariants() throws Exception {
Forge12TestBootstrap.registerVanilla();
if (zone.moddev.mc.orespawn.init.OreSpawnPatterns.registry()
.getValue(new ResourceLocation("orespawn:default")) == null) {
zone.moddev.mc.orespawn.init.OreSpawnPatterns.registerTypes(new net.minecraftforge.event.RegistryEvent.Register<>(
zone.moddev.mc.orespawn.api.OreSpawnPatternRegistry.REGISTRY_NAME,
zone.moddev.mc.orespawn.init.OreSpawnPatterns.registry()));
}
JsonObject provider = WorldgenProvider.builder("coaltest", 1)
.ore(new ResourceLocation("coaltest:coal"), new ResourceLocation("minecraft:coal_ore"),
ore -> ore.dimension(WorldIds.OVERWORLD, placement -> placement
.hostState(new ResourceLocation("minecraft:stone"), 0)))
.build().toJson();
provider.addProperty("manage_vanilla_ores", true);
String before = provider.toString();

Method geology = GeomeConfig.class.getDeclaredMethod("bake", JsonObject.class, ResourceLocation.class);
geology.setAccessible(true);
BakedGeomeConfig config = (BakedGeomeConfig) geology.invoke(null, new JsonObject(), WorldIds.OVERWORLD);
Method bake = OreSpawnOreGeneration.class.getDeclaredMethod("bakeOres", JsonObject.class, BakedGeomeConfig.class);
bake.setAccessible(true);
Object baked = bake.invoke(null, provider, config);
Field byDimension = baked.getClass().getDeclaredField("byDimension");
byDimension.setAccessible(true);
Object[] rules = (Object[]) ((Map<?, ?>) byDimension.get(baked)).get(WorldIds.OVERWORLD);
assertEquals(1, rules.length);
Method accepts = rules[0].getClass().getDeclaredMethod("accepts", IBlockState.class, Random.class, BakedGeomeConfig.class);
accepts.setAccessible(true);
for (int metadata = 0; metadata <= 6; metadata++) {
assertEquals(metadata == 0, accepts.invoke(rules[0], Blocks.STONE.getStateFromMeta(metadata), new Random(1), config));
}
assertEquals(before, provider.toString());
}
}
Loading