Merge branch '1.16' into dynregman

This commit is contained in:
CheaterCodes 2020-10-01 22:54:51 +02:00 committed by GitHub
commit bf575bf013
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
124 changed files with 1914 additions and 715 deletions
build.gradle
fabric-biome-api-v1
fabric-biomes-v1
build.gradle
src
main/java/net/fabricmc/fabric
testmod/java/net/fabricmc/fabric/test/biome
fabric-events-interaction-v0
fabric-game-rule-api-v1
build.gradle
src/main/java/net/fabricmc/fabric
fabric-item-api-v1
build.gradle
src
main
testmod
java/net/fabricmc/fabric/test/item
resources
assets/fabric-item-api-v1-testmod/models/item
fabric.mod.json
fabric-models-v0
build.gradle
src/main/java/net/fabricmc/fabric
fabric-object-builder-api-v1
build.gradle
src
main
testmod
java/net/fabricmc/fabric/test/object/builder
resources
assets/fabric-object-builder-api-v1-testmod
data/fabric-object-builder-api-v1-testmod/advancements
fabric.mod.json
fabric-registry-sync-v0
fabric-renderer-api-v1
fabric-renderer-indigo
build.gradle
src/main/java/net/fabricmc/fabric/impl/client/indigo/renderer/helper

View file

@ -18,7 +18,7 @@ plugins {
def ENV = System.getenv()
class Globals {
static def baseVersion = "0.20.2"
static def baseVersion = "0.22.1"
static def mcVersion = "1.16.3"
static def yarnVersion = "+build.1"
}

View file

@ -0,0 +1,2 @@
archivesBaseName = "fabric-biome-api-v1"
version = getSubprojectVersion(project, "2.0.0")

View file

@ -14,26 +14,33 @@
* limitations under the License.
*/
package net.fabricmc.fabric.api.biomes.v1;
package net.fabricmc.fabric.api.biome.v1;
import net.minecraft.util.registry.RegistryKey;
import net.minecraft.world.biome.Biome;
import net.fabricmc.fabric.impl.biome.InternalBiomeData;
/**
* API that exposes the internals of Minecraft's nether biome code.
*
* @deprecated Experimental feature, may be removed or changed without further notice.
* Because of the volatility of world generation in Minecraft 1.16, this API is marked experimental
* since it is likely to change in future Minecraft versions.
*/
@Deprecated
public final class NetherBiomes {
private NetherBiomes() { }
private NetherBiomes() {
}
/**
* Adds a biome to the Nether generator.
*
* @param biome The biome to add. Must not be null.
* @param biome The biome to add. Must not be null.
* @param mixedNoisePoint data about the given {@link Biome}'s spawning information in the nether.
* @see Biome.MixedNoisePoint
*/
public static void addNetherBiome(Biome biome, Biome.MixedNoisePoint mixedNoisePoint) {
public static void addNetherBiome(RegistryKey<Biome> biome, Biome.MixedNoisePoint mixedNoisePoint) {
InternalBiomeData.addNetherBiome(biome, mixedNoisePoint);
}
}

View file

@ -14,28 +14,35 @@
* limitations under the License.
*/
package net.fabricmc.fabric.api.biomes.v1;
package net.fabricmc.fabric.api.biome.v1;
import net.minecraft.util.registry.RegistryKey;
import net.minecraft.world.biome.Biome;
import net.fabricmc.fabric.impl.biome.InternalBiomeData;
/**
* API that exposes some internals of the minecraft default biome source for the overworld.
*
* @deprecated Experimental feature, may be removed or changed without further notice.
* Because of the volatility of world generation in Minecraft 1.16, this API is marked experimental
* since it is likely to change in future Minecraft versions.
*/
@Deprecated
public final class OverworldBiomes {
private OverworldBiomes() { }
private OverworldBiomes() {
}
/**
* Adds the biome to the specified climate group, with the specified weight. This is only for the biomes that make up the initial continents in generation.
*
* @param biome the biome to be added
* @param biome the biome to be added
* @param climate the climate group whereto the biome is added
* @param weight the weight of the entry. The weight in this method corresponds to its selection likelihood, with
* heavier biomes being more likely to be selected and lighter biomes being selected with less likelihood.
* @param weight the weight of the entry. The weight in this method corresponds to its selection likelihood, with
* heavier biomes being more likely to be selected and lighter biomes being selected with less likelihood.
* @see OverworldClimate for a list of vanilla biome weights
*/
public static void addContinentalBiome(Biome biome, OverworldClimate climate, double weight) {
public static void addContinentalBiome(RegistryKey<Biome> biome, OverworldClimate climate, double weight) {
InternalBiomeData.addOverworldContinentalBiome(climate, biome, weight);
}
@ -43,12 +50,12 @@ public final class OverworldBiomes {
* Adds the biome as a hills variant of the parent biome, with the specified weight.
*
* @param parent the biome to where the hills variant is added
* @param hills the biome to be set as a hills variant
* @param hills the biome to be set as a hills variant
* @param weight the weight of the entry. The weight in this method corresponds to its selection likelihood, with
* heavier biomes being more likely to be selected and lighter biomes being selected with less likelihood.
* Mods should use 1.0 as the default/normal weight.
* heavier biomes being more likely to be selected and lighter biomes being selected with less likelihood.
* Mods should use 1.0 as the default/normal weight.
*/
public static void addHillsBiome(Biome parent, Biome hills, double weight) {
public static void addHillsBiome(RegistryKey<Biome> parent, RegistryKey<Biome> hills, double weight) {
InternalBiomeData.addOverworldHillsBiome(parent, hills, weight);
}
@ -56,12 +63,12 @@ public final class OverworldBiomes {
* Adds the biome as a shore/beach biome for the parent biome, with the specified weight.
*
* @param parent the base biome to where the shore biome is added
* @param shore the biome to be added as a shore biome
* @param shore the biome to be added as a shore biome
* @param weight the weight of the entry. The weight in this method corresponds to its selection likelihood, with
* heavier biomes being more likely to be selected and lighter biomes being selected with less likelihood.
* Mods should use 1.0 as the default/normal weight.
* heavier biomes being more likely to be selected and lighter biomes being selected with less likelihood.
* Mods should use 1.0 as the default/normal weight.
*/
public static void addShoreBiome(Biome parent, Biome shore, double weight) {
public static void addShoreBiome(RegistryKey<Biome> parent, RegistryKey<Biome> shore, double weight) {
InternalBiomeData.addOverworldShoreBiome(parent, shore, weight);
}
@ -69,12 +76,12 @@ public final class OverworldBiomes {
* Adds the biome as an an edge biome (excluding as a beach) of the parent biome, with the specified weight.
*
* @param parent the base biome to where the edge biome is added
* @param edge the biome to be added as an edge biome
* @param edge the biome to be added as an edge biome
* @param weight the weight of the entry. The weight in this method corresponds to its selection likelihood, with
* heavier biomes being more likely to be selected and lighter biomes being selected with less likelihood.
* Mods should use 1.0 as the default/normal weight.
* heavier biomes being more likely to be selected and lighter biomes being selected with less likelihood.
* Mods should use 1.0 as the default/normal weight.
*/
public static void addEdgeBiome(Biome parent, Biome edge, double weight) {
public static void addEdgeBiome(RegistryKey<Biome> parent, RegistryKey<Biome> edge, double weight) {
InternalBiomeData.addOverworldEdgeBiome(parent, edge, weight);
}
@ -89,11 +96,11 @@ public final class OverworldBiomes {
* any climate assigned at this point in the generation.
*
* @param replaced the base biome that is replaced by a variant
* @param variant the biome to be added as a variant
* @param chance the chance of replacement of the biome into the variant
* @param variant the biome to be added as a variant
* @param chance the chance of replacement of the biome into the variant
* @param climates the climates in which the variants will occur in (none listed = add variant to all climates)
*/
public static void addBiomeVariant(Biome replaced, Biome variant, double chance, OverworldClimate... climates) {
public static void addBiomeVariant(RegistryKey<Biome> replaced, RegistryKey<Biome> variant, double chance, OverworldClimate... climates) {
InternalBiomeData.addOverworldBiomeReplacement(replaced, variant, chance, climates);
}
@ -102,9 +109,9 @@ public final class OverworldBiomes {
* generate in this biome.
*
* @param parent the base biome in which the river biome is to be set
* @param river the river biome for this biome
* @param river the river biome for this biome
*/
public static void setRiverBiome(Biome parent, Biome river) {
public static void setRiverBiome(RegistryKey<Biome> parent, RegistryKey<Biome> river) {
InternalBiomeData.setOverworldRiverBiome(parent, river);
}
}

View file

@ -14,11 +14,16 @@
* limitations under the License.
*/
package net.fabricmc.fabric.api.biomes.v1;
package net.fabricmc.fabric.api.biome.v1;
/**
* Represents the climates of biomes on the overworld continents.
*
* @deprecated Experimental feature, may be removed or changed without further notice.
* Because of the volatility of world generation in Minecraft 1.16, this API is marked experimental
* since it is likely to change in future Minecraft versions.
*/
@Deprecated
public enum OverworldClimate {
/**
* Includes Snowy Tundra (with a weight of 3) and Snowy Taiga (with a weight of 1).

View file

@ -16,20 +16,21 @@
package net.fabricmc.fabric.impl.biome;
import net.minecraft.util.registry.RegistryKey;
import net.minecraft.world.biome.Biome;
/**
* Represents a biome variant and its corresponding chance.
*/
final class BiomeVariant {
private final Biome variant;
private final RegistryKey<Biome> variant;
private final double chance;
/**
* @param variant the variant biome
* @param chance the chance of replacement of the biome into the variant
*/
BiomeVariant(final Biome variant, final double chance) {
BiomeVariant(final RegistryKey<Biome> variant, final double chance) {
this.variant = variant;
this.chance = chance;
}
@ -37,7 +38,7 @@ final class BiomeVariant {
/**
* @return the variant biome
*/
Biome getVariant() {
RegistryKey<Biome> getVariant() {
return variant;
}

View file

@ -16,13 +16,14 @@
package net.fabricmc.fabric.impl.biome;
import net.minecraft.util.registry.RegistryKey;
import net.minecraft.world.biome.Biome;
/**
* Represents a biome and its corresponding weight.
*/
final class ContinentalBiomeEntry {
private final Biome biome;
private final RegistryKey<Biome> biome;
private final double weight;
private final double upperWeightBound;
@ -31,13 +32,13 @@ final class ContinentalBiomeEntry {
* @param weight how often a biome will be chosen
* @param upperWeightBound the upper weight bound within the context of the other entries, used for the binary search
*/
ContinentalBiomeEntry(final Biome biome, final double weight, final double upperWeightBound) {
ContinentalBiomeEntry(final RegistryKey<Biome> biome, final double weight, final double upperWeightBound) {
this.biome = biome;
this.weight = weight;
this.upperWeightBound = upperWeightBound;
}
Biome getBiome() {
RegistryKey<Biome> getBiome() {
return biome;
}

View file

@ -0,0 +1,223 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.impl.biome;
import java.util.ArrayList;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import net.minecraft.util.registry.RegistryKey;
import net.minecraft.world.biome.Biome;
import net.minecraft.world.biome.BiomeKeys;
import net.minecraft.world.biome.layer.BiomeLayers;
import net.minecraft.world.biome.source.VanillaLayeredBiomeSource;
import net.minecraft.world.gen.feature.StructureFeature;
import net.fabricmc.fabric.api.biome.v1.OverworldClimate;
import net.fabricmc.fabric.mixin.biome.VanillaLayeredBiomeSourceAccessor;
/**
* Lists and maps for internal use only! Stores data that is used by the various mixins into the world generation
*/
public final class InternalBiomeData {
private InternalBiomeData() {
}
private static final EnumMap<OverworldClimate, WeightedBiomePicker> OVERWORLD_MODDED_CONTINENTAL_BIOME_PICKERS = new EnumMap<>(OverworldClimate.class);
private static final Map<RegistryKey<Biome>, WeightedBiomePicker> OVERWORLD_HILLS_MAP = new HashMap<>();
private static final Map<RegistryKey<Biome>, WeightedBiomePicker> OVERWORLD_SHORE_MAP = new HashMap<>();
private static final Map<RegistryKey<Biome>, WeightedBiomePicker> OVERWORLD_EDGE_MAP = new HashMap<>();
private static final Map<RegistryKey<Biome>, VariantTransformer> OVERWORLD_VARIANT_TRANSFORMERS = new HashMap<>();
private static final Map<RegistryKey<Biome>, RegistryKey<Biome>> OVERWORLD_RIVER_MAP = new HashMap<>();
private static final Set<RegistryKey<Biome>> NETHER_BIOMES = new HashSet<>();
private static final Map<RegistryKey<Biome>, Biome.MixedNoisePoint> NETHER_BIOME_NOISE_POINTS = new HashMap<>();
public static void addOverworldContinentalBiome(OverworldClimate climate, RegistryKey<Biome> biome, double weight) {
Preconditions.checkArgument(climate != null, "Climate is null");
Preconditions.checkArgument(biome != null, "Biome is null");
Preconditions.checkArgument(!Double.isNaN(weight), "Weight is NaN");
Preconditions.checkArgument(weight > 0.0, "Weight is less than or equal to 0.0 (%s)", weight);
InternalBiomeUtils.ensureIdMapping(biome);
OVERWORLD_MODDED_CONTINENTAL_BIOME_PICKERS.computeIfAbsent(climate, k -> new WeightedBiomePicker()).addBiome(biome, weight);
injectOverworldBiome(biome);
}
public static void addOverworldHillsBiome(RegistryKey<Biome> primary, RegistryKey<Biome> hills, double weight) {
Preconditions.checkArgument(primary != null, "Primary biome is null");
Preconditions.checkArgument(hills != null, "Hills biome is null");
Preconditions.checkArgument(!Double.isNaN(weight), "Weight is NaN");
Preconditions.checkArgument(weight > 0.0, "Weight is less than or equal to 0.0 (%s)", weight);
InternalBiomeUtils.ensureIdMapping(primary);
InternalBiomeUtils.ensureIdMapping(hills);
OVERWORLD_HILLS_MAP.computeIfAbsent(primary, biome -> DefaultHillsData.injectDefaultHills(primary, new WeightedBiomePicker())).addBiome(hills, weight);
injectOverworldBiome(hills);
}
public static void addOverworldShoreBiome(RegistryKey<Biome> primary, RegistryKey<Biome> shore, double weight) {
Preconditions.checkArgument(primary != null, "Primary biome is null");
Preconditions.checkArgument(shore != null, "Shore biome is null");
Preconditions.checkArgument(!Double.isNaN(weight), "Weight is NaN");
Preconditions.checkArgument(weight > 0.0, "Weight is less than or equal to 0.0 (%s)", weight);
InternalBiomeUtils.ensureIdMapping(primary);
InternalBiomeUtils.ensureIdMapping(shore);
OVERWORLD_SHORE_MAP.computeIfAbsent(primary, biome -> new WeightedBiomePicker()).addBiome(shore, weight);
injectOverworldBiome(shore);
}
public static void addOverworldEdgeBiome(RegistryKey<Biome> primary, RegistryKey<Biome> edge, double weight) {
Preconditions.checkArgument(primary != null, "Primary biome is null");
Preconditions.checkArgument(edge != null, "Edge biome is null");
Preconditions.checkArgument(!Double.isNaN(weight), "Weight is NaN");
Preconditions.checkArgument(weight > 0.0, "Weight is less than or equal to 0.0 (%s)", weight);
InternalBiomeUtils.ensureIdMapping(primary);
InternalBiomeUtils.ensureIdMapping(edge);
OVERWORLD_EDGE_MAP.computeIfAbsent(primary, biome -> new WeightedBiomePicker()).addBiome(edge, weight);
injectOverworldBiome(edge);
}
public static void addOverworldBiomeReplacement(RegistryKey<Biome> replaced, RegistryKey<Biome> variant, double chance, OverworldClimate[] climates) {
Preconditions.checkArgument(replaced != null, "Replaced biome is null");
Preconditions.checkArgument(variant != null, "Variant biome is null");
Preconditions.checkArgument(chance > 0 && chance <= 1, "Chance is not greater than 0 or less than or equal to 1");
InternalBiomeUtils.ensureIdMapping(replaced);
InternalBiomeUtils.ensureIdMapping(variant);
OVERWORLD_VARIANT_TRANSFORMERS.computeIfAbsent(replaced, biome -> new VariantTransformer()).addBiome(variant, chance, climates);
injectOverworldBiome(variant);
}
public static void setOverworldRiverBiome(RegistryKey<Biome> primary, RegistryKey<Biome> river) {
Preconditions.checkArgument(primary != null, "Primary biome is null");
InternalBiomeUtils.ensureIdMapping(primary);
InternalBiomeUtils.ensureIdMapping(river);
OVERWORLD_RIVER_MAP.put(primary, river);
if (river != null) {
injectOverworldBiome(river);
}
}
/**
* Adds the biomes in world gen to the array for the vanilla layered biome source.
* This helps with {@link VanillaLayeredBiomeSource#hasStructureFeature(StructureFeature)} returning correctly for modded biomes as well as in {@link VanillaLayeredBiomeSource#getTopMaterials()}}
*/
private static void injectOverworldBiome(RegistryKey<Biome> biome) {
List<RegistryKey<Biome>> biomes = VanillaLayeredBiomeSourceAccessor.getBIOMES();
if (biomes instanceof ImmutableList) {
biomes = new ArrayList<>(biomes);
VanillaLayeredBiomeSourceAccessor.setBIOMES(biomes);
}
biomes.add(biome);
}
public static void addNetherBiome(RegistryKey<Biome> biome, Biome.MixedNoisePoint spawnNoisePoint) {
Preconditions.checkArgument(biome != null, "Biome is null");
Preconditions.checkArgument(spawnNoisePoint != null, "Biome.MixedNoisePoint is null");
InternalBiomeUtils.ensureIdMapping(biome);
NETHER_BIOMES.add(biome);
NETHER_BIOME_NOISE_POINTS.put(biome, spawnNoisePoint);
}
public static Map<RegistryKey<Biome>, WeightedBiomePicker> getOverworldHills() {
return OVERWORLD_HILLS_MAP;
}
public static Map<RegistryKey<Biome>, WeightedBiomePicker> getOverworldShores() {
return OVERWORLD_SHORE_MAP;
}
public static Map<RegistryKey<Biome>, WeightedBiomePicker> getOverworldEdges() {
return OVERWORLD_EDGE_MAP;
}
public static Map<RegistryKey<Biome>, RegistryKey<Biome>> getOverworldRivers() {
return OVERWORLD_RIVER_MAP;
}
public static EnumMap<OverworldClimate, WeightedBiomePicker> getOverworldModdedContinentalBiomePickers() {
return OVERWORLD_MODDED_CONTINENTAL_BIOME_PICKERS;
}
public static Map<RegistryKey<Biome>, VariantTransformer> getOverworldVariantTransformers() {
return OVERWORLD_VARIANT_TRANSFORMERS;
}
public static Map<RegistryKey<Biome>, Biome.MixedNoisePoint> getNetherBiomeNoisePoints() {
return NETHER_BIOME_NOISE_POINTS;
}
private static class DefaultHillsData {
private static final ImmutableMap<RegistryKey<Biome>, RegistryKey<Biome>> DEFAULT_HILLS;
static WeightedBiomePicker injectDefaultHills(RegistryKey<Biome> base, WeightedBiomePicker picker) {
RegistryKey<Biome> defaultHill = DEFAULT_HILLS.get(base);
if (defaultHill != null) {
picker.addBiome(defaultHill, 1);
} else if (BiomeLayers.areSimilar(InternalBiomeUtils.getRawId(base), InternalBiomeUtils.getRawId(BiomeKeys.WOODED_BADLANDS_PLATEAU))) {
picker.addBiome(BiomeKeys.BADLANDS, 1);
} else if (base == BiomeKeys.DEEP_OCEAN || base == BiomeKeys.DEEP_LUKEWARM_OCEAN || base == BiomeKeys.DEEP_COLD_OCEAN) {
picker.addBiome(BiomeKeys.PLAINS, 1);
picker.addBiome(BiomeKeys.FOREST, 1);
} else if (base == BiomeKeys.DEEP_FROZEN_OCEAN) {
// Note: Vanilla Deep Frozen Oceans only have a 1/3 chance of having default hills.
// This is a clever trick that ensures that when a mod adds hills with a weight of 1, the 1/3 chance is fulfilled.
// 0.5 + 1.0 = 1.5, and 0.5 / 1.5 = 1/3.
picker.addBiome(BiomeKeys.PLAINS, 0.25);
picker.addBiome(BiomeKeys.FOREST, 0.25);
} else if (base == BiomeKeys.PLAINS) {
picker.addBiome(BiomeKeys.WOODED_HILLS, 1);
picker.addBiome(BiomeKeys.FOREST, 2);
}
return picker;
}
static {
// This map mirrors the hardcoded logic in AddHillsLayer#sample
ImmutableMap.Builder<RegistryKey<Biome>, RegistryKey<Biome>> builder = ImmutableMap.builder();
builder.put(BiomeKeys.DESERT, BiomeKeys.DESERT_HILLS);
builder.put(BiomeKeys.FOREST, BiomeKeys.WOODED_HILLS);
builder.put(BiomeKeys.BIRCH_FOREST, BiomeKeys.BIRCH_FOREST_HILLS);
builder.put(BiomeKeys.DARK_FOREST, BiomeKeys.PLAINS);
builder.put(BiomeKeys.TAIGA, BiomeKeys.TAIGA_HILLS);
builder.put(BiomeKeys.GIANT_TREE_TAIGA, BiomeKeys.GIANT_TREE_TAIGA_HILLS);
builder.put(BiomeKeys.SNOWY_TAIGA, BiomeKeys.SNOWY_TAIGA_HILLS);
builder.put(BiomeKeys.SNOWY_TUNDRA, BiomeKeys.SNOWY_MOUNTAINS);
builder.put(BiomeKeys.JUNGLE, BiomeKeys.JUNGLE_HILLS);
builder.put(BiomeKeys.BAMBOO_JUNGLE, BiomeKeys.BAMBOO_JUNGLE_HILLS);
builder.put(BiomeKeys.OCEAN, BiomeKeys.DEEP_OCEAN);
builder.put(BiomeKeys.LUKEWARM_OCEAN, BiomeKeys.DEEP_LUKEWARM_OCEAN);
builder.put(BiomeKeys.COLD_OCEAN, BiomeKeys.DEEP_COLD_OCEAN);
builder.put(BiomeKeys.FROZEN_OCEAN, BiomeKeys.DEEP_FROZEN_OCEAN);
builder.put(BiomeKeys.MOUNTAINS, BiomeKeys.WOODED_MOUNTAINS);
builder.put(BiomeKeys.SAVANNA, BiomeKeys.SAVANNA_PLATEAU);
DEFAULT_HILLS = builder.build();
}
}
}

View file

@ -20,28 +20,35 @@ import java.util.List;
import java.util.Map;
import java.util.function.IntConsumer;
import net.minecraft.util.registry.BuiltinRegistries;
import net.minecraft.util.Identifier;
import net.minecraft.world.biome.Biome;
import net.minecraft.world.biome.layer.util.LayerRandomnessSource;
import net.minecraft.world.dimension.DimensionOptions;
import net.minecraft.world.gen.GeneratorOptions;
import it.unimi.dsi.fastutil.ints.Int2IntMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import net.fabricmc.fabric.api.biomes.v1.OverworldClimate;
import net.fabricmc.fabric.mixin.biome.DimensionOptionsAccessor;
import net.fabricmc.fabric.mixin.biome.DimensionTypeAccessor;
import net.minecraft.util.registry.BuiltinRegistries;
import net.minecraft.util.registry.RegistryKey;
import net.minecraft.world.biome.Biome;
import net.minecraft.world.biome.BuiltinBiomes;
import net.minecraft.world.biome.layer.util.LayerRandomnessSource;
import net.fabricmc.fabric.api.biome.v1.OverworldClimate;
import net.fabricmc.fabric.mixin.biome.AddHillsLayerAccessor;
import net.fabricmc.fabric.mixin.biome.BuiltinBiomesAccessor;
/**
* Internal utilities used for biome sampling.
*/
public final class InternalBiomeUtils {
private InternalBiomeUtils() { }
private static final Logger LOGGER = LogManager.getLogger();
private InternalBiomeUtils() {
}
/**
* @param north raw id of the biome to the north
* @param east raw id of the biome to the east
* @param south raw id of the biome to the south
* @param west raw id of the biome to the west
* @param north raw id of the biome to the north
* @param east raw id of the biome to the east
* @param south raw id of the biome to the south
* @param west raw id of the biome to the west
* @param center central biome that comparisons are relative to
* @return whether the central biome is an edge of a biome
*/
@ -50,7 +57,7 @@ public final class InternalBiomeUtils {
}
/**
* @param mainBiomeId the main raw biome id in comparison
* @param mainBiomeId the main raw biome id in comparison
* @param secondaryBiomeId the secondary raw biome id in comparison
* @return whether the two biomes are unsimilar
*/
@ -58,21 +65,23 @@ public final class InternalBiomeUtils {
if (mainBiomeId == secondaryBiomeId) { // for efficiency, determine if the ids are equal first
return false;
} else {
Biome secondaryBiome = BuiltinRegistries.BIOME.get(secondaryBiomeId);
Biome mainBiome = BuiltinRegistries.BIOME.get(mainBiomeId);
// Regard a biome as "similar" to it's derived biome, i.e.
// No edge between plains and sunflower plains
boolean isUnsimilar = secondaryBiome.hasParent() ? !(mainBiomeId == BuiltinRegistries.BIOME.getRawId(BuiltinRegistries.BIOME.get(new Identifier(secondaryBiome.getParent())))) : true;
isUnsimilar = isUnsimilar && (mainBiome.hasParent() ? !(secondaryBiomeId == BuiltinRegistries.BIOME.getRawId(BuiltinRegistries.BIOME.get(new Identifier(mainBiome.getParent())))) : true);
return isUnsimilar;
// The parent-child relationship previously modeled in Biome itself is gone,
// and has been - for the time being - replaced by a hardcoded raw-id map
// in AddHillsLayer.
Int2IntMap parentChildMap = AddHillsLayerAccessor.getBaseToVariantMap();
return parentChildMap.get(mainBiomeId) != secondaryBiomeId
&& parentChildMap.get(secondaryBiomeId) != mainBiomeId;
}
}
/**
* @param north raw id of the biome to the north
* @param east raw id of the biome to the east
* @param east raw id of the biome to the east
* @param south raw id of the biome to the south
* @param west raw id of the biome to the west
* @param west raw id of the biome to the west
* @return whether a biome in any direction is an ocean around the central biome
*/
public static boolean neighborsOcean(int north, int east, int south, int west) {
@ -105,20 +114,21 @@ public final class InternalBiomeUtils {
/**
* Potentially transforms a biome into its variants based on the provided randomness source.
*
* @param random The randomness source
* @param random The randomness source
* @param existing The base biome
* @param climate The climate in which the biome resides, or null to indicate an unknown climate
* @param climate The climate in which the biome resides, or null to indicate an unknown climate
* @return The potentially transformed biome
*/
public static int transformBiome(LayerRandomnessSource random, Biome existing, OverworldClimate climate) {
Map<Biome, VariantTransformer> overworldVariantTransformers = InternalBiomeData.getOverworldVariantTransformers();
public static int transformBiome(LayerRandomnessSource random, RegistryKey<Biome> existing, OverworldClimate climate) {
Map<RegistryKey<Biome>, VariantTransformer> overworldVariantTransformers = InternalBiomeData.getOverworldVariantTransformers();
VariantTransformer transformer = overworldVariantTransformers.get(existing);
if (transformer != null) {
return BuiltinRegistries.BIOME.getRawId(transformer.transformBiome(existing, random, climate));
RegistryKey<Biome> key = transformer.transformBiome(existing, random, climate);
return getRawId(key);
}
return BuiltinRegistries.BIOME.getRawId(existing);
return getRawId(existing);
}
public static void injectBiomesIntoClimate(LayerRandomnessSource random, int[] vanillaArray, OverworldClimate climate, IntConsumer result) {
@ -138,7 +148,7 @@ public final class InternalBiomeUtils {
if (reqWeightSum < vanillaArray.length) {
// Vanilla biome; look it up from the vanilla array and transform accordingly.
result.accept(transformBiome(random, BuiltinRegistries.BIOME.get(vanillaArray[(int) reqWeightSum]), climate));
result.accept(transformBiome(random, BuiltinBiomes.fromRawId(vanillaArray[(int) reqWeightSum]), climate));
} else {
// Modded biome; use a binary search, and then transform accordingly.
@ -148,7 +158,22 @@ public final class InternalBiomeUtils {
}
}
public static void recreateChunkGenerators(GeneratorOptions generatorOptions) {
((DimensionOptionsAccessor) (Object) generatorOptions.getDimensionMap().get(DimensionOptions.NETHER)).setChunkGenerator(DimensionTypeAccessor.createNetherGenerator(generatorOptions.getSeed()));
public static int getRawId(RegistryKey<Biome> key) {
return BuiltinRegistries.BIOME.getRawId(BuiltinRegistries.BIOME.getOrThrow(key));
}
/**
* Makes sure that the given registry key is mapped in {@link BuiltinBiomes}. This mapping may be absent
* if mods register their biomes only in {@link BuiltinRegistries#BIOME}, and not using the
* private method in {@link BuiltinBiomes}.
*/
public static void ensureIdMapping(RegistryKey<Biome> biomeKey) {
int rawId = getRawId(biomeKey);
Int2ObjectMap<RegistryKey<Biome>> biomes = BuiltinBiomesAccessor.getBY_RAW_ID();
if (!biomes.containsKey(rawId)) {
LOGGER.debug("Automatically creating layer-related raw-id mapping for biome {}", biomeKey);
biomes.put(rawId, biomeKey);
}
}
}

View file

@ -21,10 +21,11 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import net.minecraft.util.registry.RegistryKey;
import net.minecraft.world.biome.Biome;
import net.minecraft.world.biome.layer.util.LayerRandomnessSource;
import net.fabricmc.fabric.api.biomes.v1.OverworldClimate;
import net.fabricmc.fabric.api.biome.v1.OverworldClimate;
/**
* Deals with picking variants for you.
@ -38,7 +39,7 @@ final class VariantTransformer {
* @param chance the chance of replacement of the biome into the variant
* @param climates the climates that the variant can replace the base biome in, empty/null indicates all climates
*/
void addBiome(Biome variant, double chance, OverworldClimate[] climates) {
void addBiome(RegistryKey<Biome> variant, double chance, OverworldClimate[] climates) {
if (climates == null || climates.length == 0) {
defaultTransformer.addBiome(variant, chance);
climates = OverworldClimate.values();
@ -56,7 +57,7 @@ final class VariantTransformer {
* @param random the {@link LayerRandomnessSource} from the layer
* @return the transformed biome
*/
Biome transformBiome(Biome replaced, LayerRandomnessSource random, OverworldClimate climate) {
RegistryKey<Biome> transformBiome(RegistryKey<Biome> replaced, LayerRandomnessSource random, OverworldClimate climate) {
if (climate == null) {
return defaultTransformer.transformBiome(replaced, random);
}
@ -77,7 +78,7 @@ final class VariantTransformer {
* @param variant the variant that the replaced biome is replaced with
* @param chance the chance of replacement of the biome into the variant
*/
private void addBiome(Biome variant, double chance) {
private void addBiome(RegistryKey<Biome> variant, double chance) {
variants.add(new BiomeVariant(variant, chance));
}
@ -88,7 +89,7 @@ final class VariantTransformer {
* @param random the {@link LayerRandomnessSource} from the layer
* @return the transformed biome
*/
private Biome transformBiome(Biome replaced, LayerRandomnessSource random) {
private RegistryKey<Biome> transformBiome(RegistryKey<Biome> replaced, LayerRandomnessSource random) {
for (BiomeVariant variant : variants) {
if (random.nextInt(Integer.MAX_VALUE) < variant.getChance() * Integer.MAX_VALUE) {
return variant.getVariant();

View file

@ -21,6 +21,7 @@ import java.util.List;
import com.google.common.base.Preconditions;
import net.minecraft.util.registry.RegistryKey;
import net.minecraft.world.biome.Biome;
import net.minecraft.world.biome.layer.util.LayerRandomnessSource;
@ -36,7 +37,7 @@ public final class WeightedBiomePicker {
entries = new ArrayList<>();
}
void addBiome(final Biome biome, final double weight) {
void addBiome(final RegistryKey<Biome> biome, final double weight) {
currentTotal += weight;
entries.add(new ContinentalBiomeEntry(biome, weight, currentTotal));
@ -46,7 +47,7 @@ public final class WeightedBiomePicker {
return currentTotal;
}
public Biome pickRandom(LayerRandomnessSource random) {
public RegistryKey<Biome> pickRandom(LayerRandomnessSource random) {
double target = random.nextInt(Integer.MAX_VALUE) * getCurrentWeightTotal() / Integer.MAX_VALUE;
return search(target).getBiome();

View file

@ -0,0 +1,38 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.mixin.biome;
import it.unimi.dsi.fastutil.ints.Int2IntMap;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;
import net.minecraft.world.biome.layer.AddHillsLayer;
@Mixin(AddHillsLayer.class)
public interface AddHillsLayerAccessor {
/**
* This field contains a raw-id to raw-id map for establishing parent/child relationships that
* model derived biomes.
*
* <p>For example, it contains a mapping for 1 -> 129 where 1 is the raw id of plains, while 129 is the raw id
* of the sunflower plains, which is derived from plains.
*/
@Accessor("field_26727")
static Int2IntMap getBaseToVariantMap() {
throw new AssertionError("mixin");
}
}

View file

@ -16,14 +16,18 @@
package net.fabricmc.fabric.mixin.biome;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;
import net.minecraft.world.dimension.DimensionOptions;
import net.minecraft.world.gen.chunk.ChunkGenerator;
import net.minecraft.util.registry.RegistryKey;
import net.minecraft.world.biome.Biome;
import net.minecraft.world.biome.BuiltinBiomes;
@Mixin(DimensionOptions.class)
public interface DimensionOptionsAccessor {
@Mixin(BuiltinBiomes.class)
public interface BuiltinBiomesAccessor {
@Accessor
void setChunkGenerator(ChunkGenerator generatorOptions);
static Int2ObjectMap<RegistryKey<Biome>> getBY_RAW_ID() {
throw new AssertionError("mixin");
}
}

View file

@ -21,28 +21,29 @@ import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import net.minecraft.util.registry.BuiltinRegistries;
import net.minecraft.util.registry.RegistryKey;
import net.minecraft.world.biome.Biome;
import net.minecraft.world.biome.BuiltinBiomes;
import net.minecraft.world.biome.layer.AddEdgeBiomesLayer;
import net.minecraft.world.biome.layer.util.LayerRandomnessSource;
import net.fabricmc.fabric.api.biomes.v1.OverworldBiomes;
import net.fabricmc.fabric.api.biome.v1.OverworldBiomes;
import net.fabricmc.fabric.impl.biome.InternalBiomeData;
import net.fabricmc.fabric.impl.biome.InternalBiomeUtils;
/**
* Adds edges and shores specified in {@link OverworldBiomes#addEdgeBiome(Biome, Biome, double)} and {@link OverworldBiomes#addShoreBiome(Biome, Biome, double)} to the edges layer.
* Adds edges and shores specified in {@link OverworldBiomes#addEdgeBiome(RegistryKey, RegistryKey, double)} and {@link OverworldBiomes#addShoreBiome(RegistryKey, RegistryKey, double)} to the edges layer.
*/
@Mixin(AddEdgeBiomesLayer.class)
public class MixinAddEdgeBiomesLayer {
@Inject(at = @At("HEAD"), method = "sample", cancellable = true)
private void sample(LayerRandomnessSource rand, int north, int east, int south, int west, int center, CallbackInfoReturnable<Integer> info) {
Biome centerBiome = BuiltinRegistries.BIOME.get(center);
RegistryKey<Biome> centerBiome = BuiltinBiomes.fromRawId(center);
if (InternalBiomeData.getOverworldShores().containsKey(centerBiome) && InternalBiomeUtils.neighborsOcean(north, east, south, west)) {
info.setReturnValue(BuiltinRegistries.BIOME.getRawId(InternalBiomeData.getOverworldShores().get(centerBiome).pickRandom(rand)));
info.setReturnValue(InternalBiomeUtils.getRawId(InternalBiomeData.getOverworldShores().get(centerBiome).pickRandom(rand)));
} else if (InternalBiomeData.getOverworldEdges().containsKey(centerBiome) && InternalBiomeUtils.isEdge(north, east, south, west, center)) {
info.setReturnValue(BuiltinRegistries.BIOME.getRawId(InternalBiomeData.getOverworldEdges().get(centerBiome).pickRandom(rand)));
info.setReturnValue(InternalBiomeUtils.getRawId(InternalBiomeData.getOverworldEdges().get(centerBiome).pickRandom(rand)));
}
}
}

View file

@ -16,28 +16,35 @@
package net.fabricmc.fabric.mixin.biome;
import it.unimi.dsi.fastutil.ints.Int2IntMap;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import net.minecraft.util.registry.BuiltinRegistries;
import net.minecraft.util.registry.RegistryKey;
import net.minecraft.world.biome.Biome;
import net.minecraft.world.biome.Biomes;
import net.minecraft.world.biome.BuiltinBiomes;
import net.minecraft.world.biome.layer.AddHillsLayer;
import net.minecraft.world.biome.layer.BiomeLayers;
import net.minecraft.world.biome.layer.util.LayerRandomnessSource;
import net.minecraft.world.biome.layer.util.LayerSampler;
import net.fabricmc.fabric.api.biomes.v1.OverworldBiomes;
import net.fabricmc.fabric.api.biome.v1.OverworldBiomes;
import net.fabricmc.fabric.impl.biome.InternalBiomeData;
import net.fabricmc.fabric.impl.biome.InternalBiomeUtils;
import net.fabricmc.fabric.impl.biome.WeightedBiomePicker;
/**
* Injects hills biomes specified from {@link OverworldBiomes#addHillsBiome(Biome, Biome, double)}into the default hills layer.
* Injects hills biomes specified from {@link OverworldBiomes#addHillsBiome(RegistryKey, RegistryKey, double)} into the default hills layer.
*/
@Mixin(AddHillsLayer.class)
public class MixinAddHillsLayer {
// This maps between from a biome to it's "modified variant" biome, which was previously modeled via parent biomes
@Shadow
private static Int2IntMap field_26727;
@Inject(at = @At("HEAD"), method = "sample", cancellable = true)
private void sample(LayerRandomnessSource rand, LayerSampler biomeSampler, LayerSampler noiseSampler, int chunkX, int chunkZ, CallbackInfoReturnable<Integer> info) {
if (InternalBiomeData.getOverworldHills().isEmpty()) {
@ -48,9 +55,13 @@ public class MixinAddHillsLayer {
final int biomeId = biomeSampler.sample(chunkX, chunkZ);
int noiseSample = noiseSampler.sample(chunkX, chunkZ);
int processedNoiseSample = (noiseSample - 2) % 29;
final Biome biome = BuiltinRegistries.BIOME.get(biomeId);
RegistryKey<Biome> key = BuiltinBiomes.fromRawId(biomeId);
WeightedBiomePicker hillPicker = InternalBiomeData.getOverworldHills().get(biome);
if (key == null) {
throw new IllegalStateException("Biome sampler returned unregistered Biome ID: " + biomeId);
}
WeightedBiomePicker hillPicker = InternalBiomeData.getOverworldHills().get(key);
if (hillPicker == null) {
// No hills for this biome, fall through to vanilla logic.
@ -59,12 +70,10 @@ public class MixinAddHillsLayer {
}
if (rand.nextInt(3) == 0 || processedNoiseSample == 0) {
int biomeReturn = BuiltinRegistries.BIOME.getRawId(hillPicker.pickRandom(rand));
Biome parent;
int biomeReturn = InternalBiomeUtils.getRawId(hillPicker.pickRandom(rand));
if (processedNoiseSample == 0 && biomeReturn != biomeId) {
parent = Biomes.getMutated(BuiltinRegistries.BIOME.get(biomeReturn));
biomeReturn = parent == null ? biomeId : BuiltinRegistries.BIOME.getRawId(parent);
biomeReturn = field_26727.getOrDefault(biomeReturn, biomeId);
}
if (biomeReturn != biomeId) {

View file

@ -18,42 +18,39 @@ package net.fabricmc.fabric.mixin.biome;
import java.util.Map;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import net.minecraft.util.registry.BuiltinRegistries;
import net.minecraft.util.registry.RegistryKey;
import net.minecraft.world.biome.Biome;
import net.minecraft.world.biome.BiomeKeys;
import net.minecraft.world.biome.BuiltinBiomes;
import net.minecraft.world.biome.layer.AddRiversLayer;
import net.minecraft.world.biome.layer.util.LayerRandomnessSource;
import net.minecraft.world.biome.layer.util.LayerSampler;
import net.fabricmc.fabric.api.biomes.v1.OverworldBiomes;
import net.fabricmc.fabric.api.biome.v1.OverworldBiomes;
import net.fabricmc.fabric.impl.biome.InternalBiomeData;
import net.fabricmc.fabric.impl.biome.InternalBiomeUtils;
/**
* Sets river biomes specified with {@link OverworldBiomes#setRiverBiome(Biome, Biome)}.
* Sets river biomes specified with {@link OverworldBiomes#setRiverBiome(RegistryKey, RegistryKey)}.
*/
@Mixin(AddRiversLayer.class)
public class MixinAddRiversLayer {
@Shadow
@Final
private static int RIVER_ID;
@Inject(at = @At("HEAD"), method = "sample", cancellable = true)
private void sample(LayerRandomnessSource rand, LayerSampler landSampler, LayerSampler riverSampler, int x, int z, CallbackInfoReturnable<Integer> info) {
int landBiomeId = landSampler.sample(x, z);
Biome landBiome = BuiltinRegistries.BIOME.get(landBiomeId);
RegistryKey<Biome> landBiomeKey = BuiltinBiomes.fromRawId(landBiomeId);
int riverBiomeId = riverSampler.sample(x, z);
Map<Biome, Biome> overworldRivers = InternalBiomeData.getOverworldRivers();
Map<RegistryKey<Biome>, RegistryKey<Biome>> overworldRivers = InternalBiomeData.getOverworldRivers();
if (overworldRivers.containsKey(landBiome) && riverBiomeId == RIVER_ID) {
Biome riverBiome = overworldRivers.get(landBiome);
info.setReturnValue(riverBiome == null ? landBiomeId : BuiltinRegistries.BIOME.getRawId(riverBiome));
if (overworldRivers.containsKey(landBiomeKey) && BuiltinBiomes.fromRawId(riverBiomeId) == BiomeKeys.RIVER) {
RegistryKey<Biome> riverBiome = overworldRivers.get(landBiomeKey);
info.setReturnValue(riverBiome == null ? landBiomeId : InternalBiomeUtils.getRawId(riverBiome));
}
}
}

View file

@ -23,30 +23,32 @@ import java.util.function.Supplier;
import com.mojang.datafixers.util.Pair;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import org.spongepowered.asm.mixin.injection.ModifyArgs;
import org.spongepowered.asm.mixin.injection.invoke.arg.Args;
import net.minecraft.util.registry.Registry;
import net.minecraft.world.biome.Biome;
import net.minecraft.world.biome.source.MultiNoiseBiomeSource;
import net.fabricmc.fabric.impl.biome.InternalBiomeData;
@Mixin(MultiNoiseBiomeSource.class)
/**
* This Mixin is responsible for adding mod-biomes to the NETHER preset in the MultiNoiseBiomeSource.
*/
@Mixin(MultiNoiseBiomeSource.Preset.class)
public class MixinMultiNoiseBiomeSource {
@Inject(method = "method_28467", at = @At("RETURN"))
private static void modifyNoisePoints(long l, CallbackInfoReturnable<MultiNoiseBiomeSource> cir) {
MultiNoiseBiomeSource returnedSource = cir.getReturnValue();
// collect existing noise points in non-immutable map
List<Pair<Biome.MixedNoisePoint, Supplier<Biome>>> existingPoints = new ArrayList<>(((MultiNoiseBiomeSourceAccessor) returnedSource).getBiomePoints());
// NOTE: This is a lambda-function in the NETHER preset field initializer
@ModifyArgs(method = "method_31088", at = @At(value = "INVOKE", target = "net/minecraft/world/biome/source/MultiNoiseBiomeSource.<init> (JLjava/util/List;Ljava/util/Optional;Lnet/minecraft/world/biome/source/MultiNoiseBiomeSource$1;)V"))
private static void appendNetherBiomes(Args args, MultiNoiseBiomeSource.Preset preset, Registry<Biome> registry, Long seed) {
List<Pair<Biome.MixedNoisePoint, Supplier<Biome>>> biomes = new ArrayList<>(args.get(1));
// add fabric biome noise point data to list && BiomeSource biome list
InternalBiomeData.getNetherBiomeNoisePoints().forEach((biome, noisePoint) -> {
existingPoints.add(Pair.of(noisePoint, () -> biome));
returnedSource.getBiomes().add(biome);
InternalBiomeData.getNetherBiomeNoisePoints().forEach((biomeKey, noisePoint) -> {
Biome biome = registry.getOrThrow(biomeKey);
// NOTE: Even though we have to pass in suppliers, BiomeSource's ctor will resolve them immediately
biomes.add(Pair.of(noisePoint, () -> biome));
});
// modify MultiNoiseBiomeSource list with updated data
((MultiNoiseBiomeSourceAccessor) returnedSource).setBiomePoints(existingPoints);
args.set(1, biomes);
}
}

View file

@ -24,12 +24,14 @@ import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import net.minecraft.util.registry.BuiltinRegistries;
import net.minecraft.util.registry.RegistryKey;
import net.minecraft.world.biome.Biome;
import net.minecraft.world.biome.BiomeKeys;
import net.minecraft.world.biome.BuiltinBiomes;
import net.minecraft.world.biome.layer.SetBaseBiomesLayer;
import net.minecraft.world.biome.layer.util.LayerRandomnessSource;
import net.fabricmc.fabric.api.biomes.v1.OverworldClimate;
import net.fabricmc.fabric.api.biome.v1.OverworldClimate;
import net.fabricmc.fabric.impl.biome.InternalBiomeUtils;
/**
@ -57,22 +59,6 @@ public class MixinSetBaseBiomesLayer {
@Mutable
private static int[] DRY_BIOMES;
@Shadow
@Final
private static int WOODED_BADLANDS_PLATEAU_ID;
@Shadow
@Final
private static int BADLANDS_PLATEAU_ID;
@Shadow
@Final
private static int JUNGLE_ID;
@Shadow
@Final
private static int GIANT_TREE_TAIGA_ID;
@Inject(at = @At(value = "FIELD", target = "Lnet/minecraft/world/biome/layer/SetBaseBiomesLayer;chosenGroup1:[I"), method = "sample", cancellable = true)
private void injectDryBiomes(LayerRandomnessSource random, int value, CallbackInfoReturnable<Integer> info) {
InternalBiomeUtils.injectBiomesIntoClimate(random, DRY_BIOMES, OverworldClimate.DRY, info::setReturnValue);
@ -96,16 +82,16 @@ public class MixinSetBaseBiomesLayer {
@Inject(at = @At("RETURN"), method = "sample", cancellable = true)
private void transformVariants(LayerRandomnessSource random, int value, CallbackInfoReturnable<Integer> info) {
int biomeId = info.getReturnValueI();
Biome biome = BuiltinRegistries.BIOME.get(biomeId);
RegistryKey<Biome> biome = BuiltinBiomes.fromRawId(biomeId);
// Determine what special case this is...
OverworldClimate climate;
if (biomeId == BADLANDS_PLATEAU_ID || biomeId == WOODED_BADLANDS_PLATEAU_ID) {
if (biome == BiomeKeys.BADLANDS_PLATEAU || biome == BiomeKeys.WOODED_BADLANDS_PLATEAU) {
climate = OverworldClimate.DRY;
} else if (biomeId == JUNGLE_ID) {
} else if (biome == BiomeKeys.JUNGLE) {
climate = OverworldClimate.TEMPERATE;
} else if (biomeId == GIANT_TREE_TAIGA_ID) {
} else if (biome == BiomeKeys.GIANT_TREE_TAIGA) {
climate = OverworldClimate.TEMPERATE;
} else {
climate = null;

View file

@ -0,0 +1,42 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.mixin.biome;
import java.util.List;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;
import net.minecraft.util.registry.RegistryKey;
import net.minecraft.world.biome.Biome;
import net.minecraft.world.biome.source.VanillaLayeredBiomeSource;
/**
* This accessor gives us access to the hardcoded list of overworld biomes.
*/
@Mixin(VanillaLayeredBiomeSource.class)
public interface VanillaLayeredBiomeSourceAccessor {
@Accessor
static List<RegistryKey<Biome>> getBIOMES() {
throw new AssertionError("mixin");
}
@Accessor
static void setBIOMES(List<RegistryKey<Biome>> biomes) {
throw new AssertionError("mixin");
}
}

View file

@ -3,17 +3,15 @@
"package": "net.fabricmc.fabric.mixin.biome",
"compatibilityLevel": "JAVA_8",
"mixins": [
"DimensionOptionsAccessor",
"DimensionTypeAccessor",
"AddHillsLayerAccessor",
"BuiltinBiomesAccessor",
"MixinAddEdgeBiomesLayer",
"MixinAddHillsLayer",
"MixinAddRiversLayer",
"MixinBiomeSource",
"MixinSetBaseBiomesLayer",
"MixinMinecraftDedicatedServer",
"MixinMultiNoiseBiomeSource",
"MixinVanillaLayeredBiomeSource",
"MultiNoiseBiomeSourceAccessor"
"MixinSetBaseBiomesLayer",
"MultiNoiseBiomeSourceAccessor",
"VanillaLayeredBiomeSourceAccessor"
],
"injectors": {
"defaultRequire": 1

View file

@ -1,11 +1,11 @@
{
"schemaVersion": 1,
"id": "fabric-biomes-v1",
"name": "Fabric Biomes (v1)",
"id": "fabric-biome-api-v1",
"name": "Fabric Biome API (v1)",
"version": "${version}",
"environment": "*",
"license": "Apache-2.0",
"icon": "assets/fabric-biomes-v1/icon.png",
"icon": "assets/fabric-biome-api-v1/icon.png",
"contact": {
"homepage": "https://fabricmc.net",
"irc": "irc://irc.esper.net:6667/fabric",
@ -16,11 +16,11 @@
"FabricMC"
],
"depends": {
"fabricloader": ">=0.4.0",
"minecraft": ">=1.16-alpha.20.22.a"
"fabricloader": ">=0.9.2",
"minecraft": ">=1.16.2"
},
"description": "Hooks for adding biomes to the default world generator.",
"mixins": [
"fabric-biomes-v1.mixins.json"
"fabric-biome-api-v1.mixins.json"
]
}

View file

@ -0,0 +1,67 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.test.biome;
import net.minecraft.util.Identifier;
import net.minecraft.util.registry.BuiltinRegistries;
import net.minecraft.util.registry.Registry;
import net.minecraft.util.registry.RegistryKey;
import net.minecraft.world.biome.Biome;
import net.minecraft.world.biome.BiomeKeys;
import net.minecraft.world.biome.DefaultBiomeCreator;
import net.fabricmc.api.ModInitializer;
import net.fabricmc.fabric.api.biome.v1.NetherBiomes;
import net.fabricmc.fabric.api.biome.v1.OverworldBiomes;
import net.fabricmc.fabric.api.biome.v1.OverworldClimate;
/**
* <b>NOTES FOR TESTING:</b>
* When running with this test-mod, also test this when running a dedicated server since there
* are significant differences between server + client and how they sync biomes.
*
* <p>Ingame, you can use <code>/locatebiome</code> since we use nether- and end-biomes in the overworld,
* and vice-versa, making them easy to find to verify the injection worked.
*
* <p>If you don't find a biome right away, teleport far away (~10000 blocks) from spawn and try again.
*/
public class FabricBiomeTest implements ModInitializer {
public static final String MOD_ID = "fabric-biome-api-v1-testmod";
private static final RegistryKey<Biome> TEST_CRIMSON_FOREST = RegistryKey.of(Registry.BIOME_KEY, new Identifier(MOD_ID, "test_crimson_forest"));
private static final RegistryKey<Biome> CUSTOM_PLAINS = RegistryKey.of(Registry.BIOME_KEY, new Identifier(MOD_ID, "custom_plains"));
@Override
public void onInitialize() {
Registry.register(BuiltinRegistries.BIOME, TEST_CRIMSON_FOREST.getValue(), DefaultBiomeCreator.createCrimsonForest());
NetherBiomes.addNetherBiome(BiomeKeys.BEACH, new Biome.MixedNoisePoint(0.0F, 0.5F, 0.0F, 0.0F, 0.1F));
NetherBiomes.addNetherBiome(TEST_CRIMSON_FOREST, new Biome.MixedNoisePoint(0.0F, 0.5F, 0.0F, 0.0F, 0.275F));
Registry.register(BuiltinRegistries.BIOME, CUSTOM_PLAINS.getValue(), DefaultBiomeCreator.createPlains(false));
OverworldBiomes.addBiomeVariant(BiomeKeys.PLAINS, CUSTOM_PLAINS, 1);
OverworldBiomes.addEdgeBiome(BiomeKeys.PLAINS, BiomeKeys.END_BARRENS, 0.9);
OverworldBiomes.addShoreBiome(BiomeKeys.FOREST, BiomeKeys.NETHER_WASTES, 0.9);
OverworldBiomes.addHillsBiome(BiomeKeys.BAMBOO_JUNGLE, BiomeKeys.BASALT_DELTAS, 0.9);
OverworldBiomes.addContinentalBiome(BiomeKeys.END_HIGHLANDS, OverworldClimate.DRY, 0.5);
}
}

View file

@ -1,12 +1,12 @@
{
"schemaVersion": 1,
"id": "fabric-biomes-v1-testmod",
"name": "Fabric Biomes (v1) Test Mod",
"id": "fabric-biome-api-v1-testmod",
"name": "Fabric Biome API (v1) Test Mod",
"version": "1.0.0",
"environment": "*",
"license": "Apache-2.0",
"depends": {
"fabric-biomes-v1": "*"
"fabric-biome-api-v1": "*"
},
"entrypoints": {
"main": [

View file

@ -1,2 +0,0 @@
archivesBaseName = "fabric-biomes-v1"
version = getSubprojectVersion(project, "1.0.2")

View file

@ -1,222 +0,0 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.impl.biome;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableMap;
import net.minecraft.util.registry.BuiltinRegistries;
import net.minecraft.world.biome.Biome;
import net.minecraft.world.biome.Biomes;
import net.minecraft.world.biome.layer.BiomeLayers;
import net.minecraft.world.biome.source.VanillaLayeredBiomeSource;
import net.fabricmc.fabric.api.biomes.v1.OverworldClimate;
/**
* Lists and maps for internal use only! Stores data that is used by the various mixins into the world generation
*/
public final class InternalBiomeData {
private InternalBiomeData() { }
private static final EnumMap<OverworldClimate, WeightedBiomePicker> OVERWORLD_MODDED_CONTINENTAL_BIOME_PICKERS = new EnumMap<>(OverworldClimate.class);
private static final Map<Biome, WeightedBiomePicker> OVERWORLD_HILLS_MAP = new HashMap<>();
private static final Map<Biome, WeightedBiomePicker> OVERWORLD_SHORE_MAP = new HashMap<>();
private static final Map<Biome, WeightedBiomePicker> OVERWORLD_EDGE_MAP = new HashMap<>();
private static final Map<Biome, VariantTransformer> OVERWORLD_VARIANT_TRANSFORMERS = new HashMap<>();
private static final Map<Biome, Biome> OVERWORLD_RIVER_MAP = new HashMap<>();
private static final Set<Biome> NETHER_BIOMES = new HashSet<>();
private static final Map<Biome, Biome.MixedNoisePoint> NETHER_BIOME_NOISE_POINTS = new HashMap<>();
private static final Set<Biome> SPAWN_BIOMES = new HashSet<>();
private static Method injectBiomeMethod = null;
public static void addOverworldContinentalBiome(OverworldClimate climate, Biome biome, double weight) {
Preconditions.checkArgument(climate != null, "Climate is null");
Preconditions.checkArgument(biome != null, "Biome is null");
Preconditions.checkArgument(!Double.isNaN(weight), "Weight is NaN");
Preconditions.checkArgument(weight > 0.0, "Weight is less than or equal to 0.0 (%s)", weight);
OVERWORLD_MODDED_CONTINENTAL_BIOME_PICKERS.computeIfAbsent(climate, k -> new WeightedBiomePicker()).addBiome(biome, weight);
injectOverworldBiome(biome);
}
public static void addOverworldHillsBiome(Biome primary, Biome hills, double weight) {
Preconditions.checkArgument(primary != null, "Primary biome is null");
Preconditions.checkArgument(hills != null, "Hills biome is null");
Preconditions.checkArgument(!Double.isNaN(weight), "Weight is NaN");
Preconditions.checkArgument(weight > 0.0, "Weight is less than or equal to 0.0 (%s)", weight);
OVERWORLD_HILLS_MAP.computeIfAbsent(primary, biome -> DefaultHillsData.injectDefaultHills(primary, new WeightedBiomePicker())).addBiome(hills, weight);
injectOverworldBiome(hills);
}
public static void addOverworldShoreBiome(Biome primary, Biome shore, double weight) {
Preconditions.checkArgument(primary != null, "Primary biome is null");
Preconditions.checkArgument(shore != null, "Shore biome is null");
Preconditions.checkArgument(!Double.isNaN(weight), "Weight is NaN");
Preconditions.checkArgument(weight > 0.0, "Weight is less than or equal to 0.0 (%s)", weight);
OVERWORLD_SHORE_MAP.computeIfAbsent(primary, biome -> new WeightedBiomePicker()).addBiome(shore, weight);
injectOverworldBiome(shore);
}
public static void addOverworldEdgeBiome(Biome primary, Biome edge, double weight) {
Preconditions.checkArgument(primary != null, "Primary biome is null");
Preconditions.checkArgument(edge != null, "Edge biome is null");
Preconditions.checkArgument(!Double.isNaN(weight), "Weight is NaN");
Preconditions.checkArgument(weight > 0.0, "Weight is less than or equal to 0.0 (%s)", weight);
OVERWORLD_EDGE_MAP.computeIfAbsent(primary, biome -> new WeightedBiomePicker()).addBiome(edge, weight);
injectOverworldBiome(edge);
}
public static void addOverworldBiomeReplacement(Biome replaced, Biome variant, double chance, OverworldClimate[] climates) {
Preconditions.checkArgument(replaced != null, "Replaced biome is null");
Preconditions.checkArgument(variant != null, "Variant biome is null");
Preconditions.checkArgument(chance > 0 && chance <= 1, "Chance is not greater than 0 or less than or equal to 1");
OVERWORLD_VARIANT_TRANSFORMERS.computeIfAbsent(replaced, biome -> new VariantTransformer()).addBiome(variant, chance, climates);
injectOverworldBiome(variant);
}
public static void setOverworldRiverBiome(Biome primary, Biome river) {
Preconditions.checkArgument(primary != null, "Primary biome is null");
OVERWORLD_RIVER_MAP.put(primary, river);
if (river != null) {
injectOverworldBiome(river);
}
}
public static void addSpawnBiome(Biome biome) {
Preconditions.checkArgument(biome != null, "Biome is null");
SPAWN_BIOMES.add(biome);
}
private static void injectOverworldBiome(Biome biome) {
try {
if (injectBiomeMethod == null) {
injectBiomeMethod = VanillaLayeredBiomeSource.class.getDeclaredMethod("fabric_injectBiome", Biome.class);
injectBiomeMethod.setAccessible(true);
}
injectBiomeMethod.invoke(null, biome);
} catch (NoSuchMethodException | SecurityException | IllegalAccessException | InvocationTargetException e) {
throw new RuntimeException(e);
}
}
public static void addNetherBiome(Biome biome, Biome.MixedNoisePoint spawnNoisePoint) {
Preconditions.checkArgument(biome != null, "Biome is null");
Preconditions.checkArgument(spawnNoisePoint != null, "Biome.MixedNoisePoint is null");
NETHER_BIOMES.add(biome);
NETHER_BIOME_NOISE_POINTS.put(biome, spawnNoisePoint);
}
public static Set<Biome> getSpawnBiomes() {
return SPAWN_BIOMES;
}
public static Map<Biome, WeightedBiomePicker> getOverworldHills() {
return OVERWORLD_HILLS_MAP;
}
public static Map<Biome, WeightedBiomePicker> getOverworldShores() {
return OVERWORLD_SHORE_MAP;
}
public static Map<Biome, WeightedBiomePicker> getOverworldEdges() {
return OVERWORLD_EDGE_MAP;
}
public static Map<Biome, Biome> getOverworldRivers() {
return OVERWORLD_RIVER_MAP;
}
public static EnumMap<OverworldClimate, WeightedBiomePicker> getOverworldModdedContinentalBiomePickers() {
return OVERWORLD_MODDED_CONTINENTAL_BIOME_PICKERS;
}
public static Map<Biome, VariantTransformer> getOverworldVariantTransformers() {
return OVERWORLD_VARIANT_TRANSFORMERS;
}
public static Set<Biome> getNetherBiomes() {
return Collections.unmodifiableSet(NETHER_BIOMES);
}
public static Map<Biome, Biome.MixedNoisePoint> getNetherBiomeNoisePoints() {
return NETHER_BIOME_NOISE_POINTS;
}
private static class DefaultHillsData {
private static final ImmutableMap<Biome, Biome> DEFAULT_HILLS;
static WeightedBiomePicker injectDefaultHills(Biome base, WeightedBiomePicker picker) {
Biome defaultHill = DEFAULT_HILLS.get(base);
if (defaultHill != null) {
picker.addBiome(defaultHill, 1);
} else if (BiomeLayers.areSimilar(BuiltinRegistries.BIOME.getRawId(base), BuiltinRegistries.BIOME.getRawId(Biomes.WOODED_BADLANDS_PLATEAU))) {
picker.addBiome(Biomes.BADLANDS, 1);
} else if (base == Biomes.DEEP_OCEAN || base == Biomes.DEEP_LUKEWARM_OCEAN || base == Biomes.DEEP_COLD_OCEAN) {
picker.addBiome(Biomes.PLAINS, 1);
picker.addBiome(Biomes.FOREST, 1);
} else if (base == Biomes.DEEP_FROZEN_OCEAN) {
// Note: Vanilla Deep Frozen Oceans only have a 1/3 chance of having default hills.
// This is a clever trick that ensures that when a mod adds hills with a weight of 1, the 1/3 chance is fulfilled.
// 0.5 + 1.0 = 1.5, and 0.5 / 1.5 = 1/3.
picker.addBiome(Biomes.PLAINS, 0.25);
picker.addBiome(Biomes.FOREST, 0.25);
} else if (base == Biomes.PLAINS) {
picker.addBiome(Biomes.WOODED_HILLS, 1);
picker.addBiome(Biomes.FOREST, 2);
}
return picker;
}
static {
ImmutableMap.Builder<Biome, Biome> builder = ImmutableMap.builder();
builder.put(Biomes.DESERT, Biomes.DESERT_HILLS);
builder.put(Biomes.FOREST, Biomes.WOODED_HILLS);
builder.put(Biomes.BIRCH_FOREST, Biomes.BIRCH_FOREST_HILLS);
builder.put(Biomes.DARK_FOREST, Biomes.PLAINS);
builder.put(Biomes.TAIGA, Biomes.TAIGA_HILLS);
builder.put(Biomes.GIANT_TREE_TAIGA, Biomes.GIANT_TREE_TAIGA_HILLS);
builder.put(Biomes.SNOWY_TAIGA, Biomes.SNOWY_TAIGA_HILLS);
builder.put(Biomes.SNOWY_TUNDRA, Biomes.SNOWY_MOUNTAINS);
builder.put(Biomes.JUNGLE, Biomes.JUNGLE_HILLS);
builder.put(Biomes.BAMBOO_JUNGLE, Biomes.BAMBOO_JUNGLE_HILLS);
builder.put(Biomes.OCEAN, Biomes.DEEP_OCEAN);
builder.put(Biomes.LUKEWARM_OCEAN, Biomes.DEEP_LUKEWARM_OCEAN);
builder.put(Biomes.COLD_OCEAN, Biomes.DEEP_COLD_OCEAN);
builder.put(Biomes.FROZEN_OCEAN, Biomes.DEEP_FROZEN_OCEAN);
builder.put(Biomes.MOUNTAINS, Biomes.WOODED_MOUNTAINS);
builder.put(Biomes.SAVANNA, Biomes.SAVANNA_PLATEAU);
DEFAULT_HILLS = builder.build();
}
}
}

View file

@ -1,53 +0,0 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.mixin.biome;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import net.minecraft.world.biome.Biome;
import net.minecraft.world.biome.source.BiomeSource;
import net.fabricmc.fabric.impl.biome.InternalBiomeData;
/**
* Adds spawn biomes to the base {@link BiomeSource} class.
*/
@Mixin(BiomeSource.class)
public class MixinBiomeSource {
@Shadow
@Final
private static List<Biome> SPAWN_BIOMES;
@Inject(at = @At("RETURN"), cancellable = true, method = "getSpawnBiomes")
private void getSpawnBiomes(CallbackInfoReturnable<List<Biome>> info) {
Set<Biome> biomes = new LinkedHashSet<>(info.getReturnValue());
if (biomes.addAll(InternalBiomeData.getSpawnBiomes())) {
info.setReturnValue(new ArrayList<>(biomes));
}
}
}

View file

@ -1,54 +0,0 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.mixin.biome;
import java.util.ArrayList;
import java.util.List;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Mutable;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import net.minecraft.world.biome.Biome;
import net.minecraft.world.biome.source.VanillaLayeredBiomeSource;
import net.minecraft.world.gen.feature.StructureFeature;
/**
* Adds the biomes in world gen to the array for the vanilla layered biome source.
* This helps with {@link VanillaLayeredBiomeSource#hasStructureFeature(StructureFeature)} returning correctly for modded biomes as well as in {@link VanillaLayeredBiomeSource#getTopMaterials()}}
*/
@Mixin(VanillaLayeredBiomeSource.class)
public class MixinVanillaLayeredBiomeSource {
@Shadow
@Final
@Mutable
private static List<Biome> BIOMES;
@Inject(method = "<clinit>", at = @At("RETURN"))
private static void cinit(CallbackInfo info) {
BIOMES = new ArrayList<>(BIOMES);
}
//Called via reflection
private static void fabric_injectBiome(Biome biome) {
BIOMES.add(biome);
}
}

View file

@ -1,41 +0,0 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.test.biome;
import net.minecraft.world.biome.DefaultBiomeCreator;
import net.minecraft.util.Identifier;
import net.minecraft.util.registry.BuiltinRegistries;
import net.minecraft.util.registry.Registry;
import net.minecraft.world.biome.Biome;
import net.minecraft.world.biome.Biomes;
import net.fabricmc.api.ModInitializer;
import net.fabricmc.fabric.api.biomes.v1.NetherBiomes;
import net.fabricmc.fabric.api.biomes.v1.OverworldBiomes;
public class FabricBiomeTest implements ModInitializer {
public static final String MOD_ID = "fabric-biome-api-v1-testmod";
@Override public void onInitialize() {
Biome biome = Registry.register(BuiltinRegistries.BIOME, new Identifier(MOD_ID, "test_crimson_forest"), DefaultBiomeCreator.createCrimsonForest());
NetherBiomes.addNetherBiome(Biomes.BEACH, new Biome.MixedNoisePoint(0.0F, 0.5F, 0.0F, 0.0F, 0.1F));
NetherBiomes.addNetherBiome(biome, new Biome.MixedNoisePoint(0.0F, 0.5F, 0.0F, 0.0F, 0.275F));
Biome customPlains = Registry.register(BuiltinRegistries.BIOME, new Identifier(MOD_ID, "custom_plains"), DefaultBiomeCreator.createPlains(null, false));
OverworldBiomes.addBiomeVariant(Biomes.PLAINS, customPlains, 1);
}
}

View file

@ -1,5 +1,5 @@
archivesBaseName = "fabric-events-interaction-v0"
version = getSubprojectVersion(project, "0.4.0")
version = getSubprojectVersion(project, "0.4.1")
dependencies {
compile project(path: ':fabric-api-base', configuration: 'dev')

View file

@ -16,6 +16,8 @@
package net.fabricmc.fabric.api.block;
import org.jetbrains.annotations.Nullable;
import net.minecraft.block.BlockState;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.ItemStack;
@ -24,5 +26,5 @@ import net.minecraft.util.math.BlockPos;
import net.minecraft.world.BlockView;
public interface BlockPickInteractionAware {
ItemStack getPickedStack(BlockState state, BlockView view, BlockPos pos, /* nullable */ PlayerEntity player, /* nullable */ HitResult result);
ItemStack getPickedStack(BlockState state, BlockView view, BlockPos pos, @Nullable PlayerEntity player, @Nullable HitResult result);
}

View file

@ -16,10 +16,12 @@
package net.fabricmc.fabric.api.entity;
import org.jetbrains.annotations.Nullable;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.item.ItemStack;
import net.minecraft.util.hit.HitResult;
public interface EntityPickInteractionAware {
ItemStack getPickedStack(/* nullable */ PlayerEntity player, /* nullable */ HitResult result);
ItemStack getPickedStack(@Nullable PlayerEntity player, @Nullable HitResult result);
}

View file

@ -16,6 +16,8 @@
package net.fabricmc.fabric.api.event.player;
import org.jetbrains.annotations.Nullable;
import net.minecraft.entity.Entity;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.util.ActionResult;
@ -50,5 +52,5 @@ public interface AttackEntityCallback {
}
);
ActionResult interact(PlayerEntity player, World world, Hand hand, Entity entity, /* Nullable */ EntityHitResult hitResult);
ActionResult interact(PlayerEntity player, World world, Hand hand, Entity entity, @Nullable EntityHitResult hitResult);
}

View file

@ -16,6 +16,8 @@
package net.fabricmc.fabric.api.event.player;
import org.jetbrains.annotations.Nullable;
import net.minecraft.entity.Entity;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.util.ActionResult;
@ -50,5 +52,5 @@ public interface UseEntityCallback {
}
);
ActionResult interact(PlayerEntity player, World world, Hand hand, Entity entity, /* Nullable */ EntityHitResult hitResult);
ActionResult interact(PlayerEntity player, World world, Hand hand, Entity entity, @Nullable EntityHitResult hitResult);
}

View file

@ -1,5 +1,5 @@
archivesBaseName = "fabric-game-rule-api-v1"
version = getSubprojectVersion(project, "1.0.1")
version = getSubprojectVersion(project, "1.0.2")
minecraft {
accessWidener = file("src/main/resources/fabric-game-rule-api-v1.accesswidener")

View file

@ -22,6 +22,7 @@ import java.util.function.BiConsumer;
import com.mojang.brigadier.arguments.DoubleArgumentType;
import com.mojang.brigadier.arguments.IntegerArgumentType;
import org.jetbrains.annotations.Nullable;
import net.minecraft.server.MinecraftServer;
import net.minecraft.world.GameRules;
@ -140,7 +141,7 @@ public final class GameRuleFactory {
* @param changedCallback a callback that is invoked when the value of a game rule has changed
* @return an integer rule type
*/
public static GameRules.Type<GameRules.IntRule> createIntRule(int defaultValue, int minimumValue, int maximumValue, /* @Nullable */ BiConsumer<MinecraftServer, GameRules.IntRule> changedCallback) {
public static GameRules.Type<GameRules.IntRule> createIntRule(int defaultValue, int minimumValue, int maximumValue, @Nullable BiConsumer<MinecraftServer, GameRules.IntRule> changedCallback) {
return new GameRules.Type<>(
() -> IntegerArgumentType.integer(minimumValue, maximumValue),
type -> new BoundedIntRule(type, defaultValue, minimumValue, maximumValue), // Internally use a bounded int rule

View file

@ -26,6 +26,7 @@ import java.util.List;
import com.mojang.brigadier.context.CommandContext;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.Nullable;
import net.minecraft.server.MinecraftServer;
import net.minecraft.server.command.ServerCommandSource;
@ -144,7 +145,7 @@ public final class EnumRule<E extends Enum<E>> extends GameRules.Rule<EnumRule<E
return this.supportedValues.contains(value);
}
public void set(E value, /* @Nullable */ MinecraftServer server) throws IllegalArgumentException {
public void set(E value, @Nullable MinecraftServer server) throws IllegalArgumentException {
checkNotNull(value);
if (!this.supports(value)) {

View file

@ -16,10 +16,12 @@
package net.fabricmc.fabric.impl.gamerule;
import org.jetbrains.annotations.Nullable;
import net.fabricmc.fabric.api.gamerule.v1.CustomGameRuleCategory;
public interface RuleKeyExtensions {
/* @Nullable */
@Nullable
CustomGameRuleCategory fabric_getCustomCategory();
void fabric_setCustomCategory(CustomGameRuleCategory customCategory);

View file

@ -16,6 +16,7 @@
package net.fabricmc.fabric.mixin.gamerule;
import org.jetbrains.annotations.Nullable;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
@ -26,8 +27,8 @@ import net.fabricmc.fabric.impl.gamerule.RuleKeyExtensions;
@Mixin(GameRules.Key.class)
public abstract class RuleKeyMixin implements RuleKeyExtensions {
/* @Nullable */
@Unique
@Nullable
private CustomGameRuleCategory customCategory;
@Override

View file

@ -1,5 +1,5 @@
archivesBaseName = "fabric-item-api-v1"
version = getSubprojectVersion(project, "1.1.0")
version = getSubprojectVersion(project, "1.2.0")
dependencies {
compile project(path: ':fabric-api-base', configuration: 'dev')

View file

@ -0,0 +1,41 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.api.item.v1;
import java.util.function.Consumer;
import net.minecraft.entity.LivingEntity;
import net.minecraft.item.ItemStack;
/**
* Allows an item to run custom logic when {@link ItemStack#damage(int, LivingEntity, Consumer)} is called.
* This is useful for items that, for example, may drain durability from some other source before damaging
* the stack itself.
*
* <p>Custom damage handlers can be set with {@link FabricItemSettings#customDamage}.
*/
@FunctionalInterface
public interface CustomDamageHandler {
/**
* Called to apply damage to the given stack.
* This can be used to e.g. drain from a battery before actually damaging the item.
* @param amount The amount of damage originally requested
* @param breakCallback Callback when the stack reaches zero damage. See {@link ItemStack#damage(int, LivingEntity, Consumer)} and its callsites for more information.
* @return The amount of damage to pass to vanilla's logic
*/
int damage(ItemStack stack, int amount, LivingEntity entity, Consumer<LivingEntity> breakCallback);
}

View file

@ -19,6 +19,7 @@ package net.fabricmc.fabric.api.item.v1;
import net.minecraft.item.FoodComponent;
import net.minecraft.item.Item;
import net.minecraft.item.ItemGroup;
import net.minecraft.item.ItemStack;
import net.minecraft.util.Rarity;
import net.fabricmc.fabric.impl.item.FabricItemInternals;
@ -42,6 +43,16 @@ public class FabricItemSettings extends Item.Settings {
return this;
}
/**
* Sets the custom damage handler of the item.
* Note that this is only called on an ItemStack if {@link ItemStack#isDamageable()} returns true.
* @see CustomDamageHandler
*/
public FabricItemSettings customDamage(CustomDamageHandler handler) {
FabricItemInternals.computeExtraData(this).customDamage(handler);
return this;
}
// Overrides of vanilla methods
@Override

View file

@ -20,6 +20,7 @@ import java.util.WeakHashMap;
import net.minecraft.item.Item;
import net.fabricmc.fabric.api.item.v1.CustomDamageHandler;
import net.fabricmc.fabric.api.item.v1.EquipmentSlotProvider;
public final class FabricItemInternals {
@ -37,14 +38,20 @@ public final class FabricItemInternals {
if (data != null) {
((ItemExtensions) item).fabric_setEquipmentSlotProvider(data.equipmentSlotProvider);
((ItemExtensions) item).fabric_setCustomDamageHandler(data.customDamageHandler);
}
}
public static final class ExtraData {
private /* @Nullable */ EquipmentSlotProvider equipmentSlotProvider;
private /* @Nullable */ CustomDamageHandler customDamageHandler;
public void equipmentSlot(EquipmentSlotProvider equipmentSlotProvider) {
this.equipmentSlotProvider = equipmentSlotProvider;
}
public void customDamage(CustomDamageHandler handler) {
this.customDamageHandler = handler;
}
}
}

View file

@ -16,9 +16,12 @@
package net.fabricmc.fabric.impl.item;
import net.fabricmc.fabric.api.item.v1.CustomDamageHandler;
import net.fabricmc.fabric.api.item.v1.EquipmentSlotProvider;
public interface ItemExtensions {
EquipmentSlotProvider fabric_getEquipmentSlotProvider();
/* @Nullable */ EquipmentSlotProvider fabric_getEquipmentSlotProvider();
void fabric_setEquipmentSlotProvider(EquipmentSlotProvider equipmentSlotProvider);
/* @Nullable */ CustomDamageHandler fabric_getCustomDamageHandler();
void fabric_setCustomDamageHandler(CustomDamageHandler handler);
}

View file

@ -24,6 +24,7 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import net.minecraft.item.Item;
import net.fabricmc.fabric.api.item.v1.CustomDamageHandler;
import net.fabricmc.fabric.api.item.v1.EquipmentSlotProvider;
import net.fabricmc.fabric.impl.item.FabricItemInternals;
import net.fabricmc.fabric.impl.item.ItemExtensions;
@ -33,6 +34,9 @@ abstract class ItemMixin implements ItemExtensions {
@Unique
private EquipmentSlotProvider equipmentSlotProvider;
@Unique
private CustomDamageHandler customDamageHandler;
@Inject(method = "<init>", at = @At("RETURN"))
private void onConstruct(Item.Settings settings, CallbackInfo info) {
FabricItemInternals.onBuild(settings, (Item) (Object) this);
@ -47,4 +51,14 @@ abstract class ItemMixin implements ItemExtensions {
public void fabric_setEquipmentSlotProvider(EquipmentSlotProvider equipmentSlotProvider) {
this.equipmentSlotProvider = equipmentSlotProvider;
}
@Override
public CustomDamageHandler fabric_getCustomDamageHandler() {
return customDamageHandler;
}
@Override
public void fabric_setCustomDamageHandler(CustomDamageHandler handler) {
this.customDamageHandler = handler;
}
}

View file

@ -0,0 +1,68 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.mixin.item;
import java.util.function.Consumer;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.ModifyArg;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import net.minecraft.entity.LivingEntity;
import net.minecraft.item.Item;
import net.minecraft.item.ItemStack;
import net.fabricmc.fabric.api.item.v1.CustomDamageHandler;
import net.fabricmc.fabric.impl.item.ItemExtensions;
@Mixin(ItemStack.class)
public abstract class ItemStackMixin {
@Shadow public abstract Item getItem();
@Unique
private LivingEntity fabric_damagingEntity;
@Unique
private Consumer<LivingEntity> fabric_breakCallback;
@Inject(method = "damage(ILnet/minecraft/entity/LivingEntity;Ljava/util/function/Consumer;)V", at = @At("HEAD"))
private void saveDamager(int amount, LivingEntity entity, Consumer<LivingEntity> breakCallback, CallbackInfo ci) {
this.fabric_damagingEntity = entity;
this.fabric_breakCallback = breakCallback;
}
@ModifyArg(method = "damage(ILnet/minecraft/entity/LivingEntity;Ljava/util/function/Consumer;)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/item/ItemStack;damage(ILjava/util/Random;Lnet/minecraft/server/network/ServerPlayerEntity;)Z"), index = 0)
private int hookDamage(int amount) {
CustomDamageHandler handler = ((ItemExtensions) getItem()).fabric_getCustomDamageHandler();
if (handler != null) {
return handler.damage((ItemStack) (Object) this, amount, fabric_damagingEntity, fabric_breakCallback);
}
return amount;
}
@Inject(method = "damage(ILnet/minecraft/entity/LivingEntity;Ljava/util/function/Consumer;)V", at = @At("RETURN"))
private <T extends LivingEntity> void clearDamager(int amount, T entity, Consumer<T> breakCallback, CallbackInfo ci) {
this.fabric_damagingEntity = null;
this.fabric_breakCallback = null;
}
}

View file

@ -3,6 +3,7 @@
"package": "net.fabricmc.fabric.mixin.item",
"compatibilityLevel": "JAVA_8",
"mixins": [
"ItemStackMixin",
"ItemMixin",
"MobEntityMixin"
],

View file

@ -0,0 +1,59 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.test.item;
import net.minecraft.item.ItemStack;
import net.minecraft.item.PickaxeItem;
import net.minecraft.item.ToolMaterials;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.text.Text;
import net.minecraft.util.Identifier;
import net.minecraft.util.registry.Registry;
import net.fabricmc.api.ModInitializer;
import net.fabricmc.fabric.api.item.v1.CustomDamageHandler;
import net.fabricmc.fabric.api.item.v1.FabricItemSettings;
public class CustomDamageTest implements ModInitializer {
@Override
public void onInitialize() {
Registry.register(Registry.ITEM, new Identifier("fabric-item-api-v1-testmod", "weird_pickaxe"), new WeirdPick());
}
public static final CustomDamageHandler WEIRD_DAMAGE_HANDLER = (stack, amount, entity, breakCallback) -> {
// If sneaking, apply all damage to vanilla. Otherwise, increment a tag on the stack by one and don't apply any damage
if (entity.isSneaking()) {
return amount;
} else {
CompoundTag tag = stack.getOrCreateTag();
tag.putInt("weird", tag.getInt("weird") + 1);
return 0;
}
};
public static class WeirdPick extends PickaxeItem {
protected WeirdPick() {
super(ToolMaterials.GOLD, 1, -2.8F, new FabricItemSettings().customDamage(WEIRD_DAMAGE_HANDLER));
}
@Override
public Text getName(ItemStack stack) {
int v = stack.getOrCreateTag().getInt("weird");
return super.getName(stack).shallowCopy().append(" (Weird Value: " + v + ")");
}
}
}

View file

@ -0,0 +1,6 @@
{
"parent": "minecraft:item/handheld",
"textures": {
"layer0": "minecraft:item/golden_pickaxe"
}
}

View file

@ -14,6 +14,9 @@
],
"client": [
"net.fabricmc.fabric.test.item.client.TooltipTests"
],
"main": [
"net.fabricmc.fabric.test.item.CustomDamageTest"
]
}
}

View file

@ -1,5 +1,5 @@
archivesBaseName = "fabric-models-v0"
version = getSubprojectVersion(project, "0.1.0")
version = getSubprojectVersion(project, "0.1.1")
dependencies {
compile project(path: ':fabric-api-base', configuration: 'dev')

View file

@ -16,6 +16,8 @@
package net.fabricmc.fabric.api.client.model;
import org.jetbrains.annotations.Nullable;
import net.minecraft.client.render.model.UnbakedModel;
import net.minecraft.util.Identifier;
@ -46,5 +48,6 @@ public interface ModelResourceProvider {
* @return The loaded UnbakedModel, or null if this ModelResourceProvider doesn't handle a specific Identifier
* (or if there was no error!).
*/
/* @Nullable */ UnbakedModel loadModelResource(Identifier resourceId, ModelProviderContext context) throws ModelProviderException;
@Nullable
UnbakedModel loadModelResource(Identifier resourceId, ModelProviderContext context) throws ModelProviderException;
}

View file

@ -16,6 +16,8 @@
package net.fabricmc.fabric.api.client.model;
import org.jetbrains.annotations.Nullable;
import net.minecraft.client.render.model.UnbakedModel;
import net.minecraft.client.util.ModelIdentifier;
@ -43,5 +45,6 @@ public interface ModelVariantProvider {
* @return The loaded UnbakedModel, or null if this ModelVariantProvider doesn't handle a specific Identifier
* (or if there was no error!).
*/
/* @Nullable */ UnbakedModel loadModelVariant(ModelIdentifier modelId, ModelProviderContext context) throws ModelProviderException;
@Nullable
UnbakedModel loadModelVariant(ModelIdentifier modelId, ModelProviderContext context) throws ModelProviderException;
}

View file

@ -27,6 +27,7 @@ import java.util.stream.Collectors;
import com.google.common.collect.Lists;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.Nullable;
import net.minecraft.client.render.model.ModelLoader;
import net.minecraft.client.render.model.UnbakedModel;
@ -139,12 +140,12 @@ public class ModelLoadingRegistryImpl implements ModelLoadingRegistry {
}
}
/* @Nullable */
@Nullable
public UnbakedModel loadModelFromResource(Identifier resourceId) {
return loadCustomModel((r) -> r.loadModelResource(resourceId, this), modelResourceProviders, "resource provider");
}
/* @Nullable */
@Nullable
public UnbakedModel loadModelFromVariant(Identifier variantId) {
if (!(variantId instanceof ModelIdentifier)) {
return loadModelFromResource(variantId);

View file

@ -1,9 +1,11 @@
archivesBaseName = "fabric-object-builder-api-v1"
version = getSubprojectVersion(project, "1.7.0")
version = getSubprojectVersion(project, "1.8.1")
dependencies {
compile project(path: ':fabric-api-base', configuration: 'dev')
compile project(path: ':fabric-tool-attribute-api-v1', configuration: 'dev')
testmodCompile project(path: ':fabric-command-api-v1', configuration: 'dev')
}
minecraft {

View file

@ -0,0 +1,46 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.api.object.builder.v1.advancement;
import net.minecraft.advancement.criterion.Criterion;
import net.minecraft.util.Identifier;
import net.fabricmc.fabric.mixin.object.builder.CriteriaAccessor;
/**
* Allows registering advancement criteria for triggers.
*
* <p>A registered criterion (trigger) can be retrieved through
* {@link net.minecraft.advancement.criterion.Criteria#getById(Identifier)}.</p>
*
* @see net.minecraft.advancement.criterion.Criteria
*/
public final class CriterionRegistry {
/**
* Registers a criterion for a trigger for advancements.
*
* @param <T> the criterion's type
* @param criterion the criterion registered
* @return the criterion registered, for chaining
* @throws IllegalArgumentException if a criterion with the same {@link
* Criterion#getId() id} exists
*/
public static <T extends Criterion<?>> T register(T criterion) {
CriteriaAccessor.callRegister(criterion);
return criterion;
}
}

View file

@ -0,0 +1,67 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.api.object.builder.v1.trade;
import java.util.List;
import java.util.function.Consumer;
import net.minecraft.village.TradeOffers;
import net.minecraft.village.VillagerProfession;
import net.fabricmc.fabric.impl.object.builder.TradeOfferInternals;
/**
* Utilities to help with registration of trade offers.
*/
public final class TradeOfferHelper {
/**
* Registers trade offer factories for use by villagers.
*
* <p>Below is an example, of registering a trade off factory to be added a blacksmith with a profession level of 3:
* <blockquote><pre>
* TradeOfferHelper.registerVillagerOffers(VillagerProfession.BLACKSMITH, 3, factories -> {
* factories.add(new CustomTradeFactory(...);
* });
* </pre></blockquote>
*
* @param profession the villager profession to assign the trades to
* @param level the profession level the villager must be to offer the trades
* @param factories a consumer to provide the factories
*/
public static void registerVillagerOffers(VillagerProfession profession, int level, Consumer<List<TradeOffers.Factory>> factories) {
TradeOfferInternals.registerVillagerOffers(profession, level, factories);
}
/**
* Registers trade offer factories for use by wandering trades.
*
* @param level the level the trades
* @param factory a consumer to provide the factories
*/
public static void registerWanderingTraderOffers(int level, Consumer<List<TradeOffers.Factory>> factory) {
TradeOfferInternals.registerWanderingTraderOffers(level, factory);
}
/**
* Refreshes the trade list by resetting the trade lists to vanilla state, and then registering all trade offers again.
*
* <p>This method is geared for use by mods which for example provide data driven villager trades.
*/
public static void refreshOffers() {
TradeOfferInternals.refreshOffers();
}
}

View file

@ -19,6 +19,7 @@ package net.fabricmc.fabric.api.object.builder.v1.villager;
import static com.google.common.base.Preconditions.checkState;
import com.google.common.collect.ImmutableSet;
import org.jetbrains.annotations.Nullable;
import net.minecraft.block.Block;
import net.minecraft.block.Blocks;
@ -46,7 +47,7 @@ public final class VillagerProfessionBuilder {
private final ImmutableSet.Builder<Block> secondaryJobSiteBlockBuilder = ImmutableSet.builder();
private Identifier identifier;
private PointOfInterestType pointOfInterestType;
/* @Nullable */
@Nullable
private SoundEvent workSoundEvent;
private VillagerProfessionBuilder() {
@ -54,6 +55,7 @@ public final class VillagerProfessionBuilder {
/**
* Creates a builder instance to allow for creation of a {@link VillagerProfession}.
*
* @return A new builder.
*/
public static VillagerProfessionBuilder create() {
@ -140,7 +142,7 @@ public final class VillagerProfessionBuilder {
* @param workSoundEvent The {@link SoundEvent} to be played.
* @return this builder.
*/
public VillagerProfessionBuilder workSound(/* @Nullable */ SoundEvent workSoundEvent) {
public VillagerProfessionBuilder workSound(@Nullable SoundEvent workSoundEvent) {
this.workSoundEvent = workSoundEvent;
return this;
}

View file

@ -19,6 +19,8 @@ package net.fabricmc.fabric.impl.object.builder;
import java.util.ArrayList;
import java.util.List;
import org.jetbrains.annotations.Nullable;
import net.minecraft.block.Block;
import net.minecraft.item.Item;
import net.minecraft.tag.Tag;
@ -56,7 +58,8 @@ public final class FabricBlockInternals {
public static final class ExtraData {
private final List<MiningLevel> miningLevels = new ArrayList<>();
/* @Nullable */ private Boolean breakByHand;
@Nullable
private Boolean breakByHand;
public ExtraData(Block.Settings settings) {
}

View file

@ -0,0 +1,122 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.impl.object.builder;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import org.apache.commons.lang3.ArrayUtils;
import net.minecraft.village.TradeOffers;
import net.minecraft.village.VillagerProfession;
import net.fabricmc.fabric.mixin.object.builder.TradeOffersAccessor;
public final class TradeOfferInternals {
/**
* A copy of the original trade offers map.
*/
public static Map<VillagerProfession, Int2ObjectMap<TradeOffers.Factory[]>> DEFAULT_VILLAGER_OFFERS;
public static Int2ObjectMap<TradeOffers.Factory[]> DEFAULT_WANDERING_TRADER_OFFERS;
private static final Map<VillagerProfession, Int2ObjectMap<TradeOffers.Factory[]>> VILLAGER_TRADE_FACTORIES = new HashMap<>();
private static final Int2ObjectMap<TradeOffers.Factory[]> WANDERING_TRADER_FACTORIES = new Int2ObjectOpenHashMap<>();
private TradeOfferInternals() {
}
public static void registerVillagerOffers(VillagerProfession profession, int level, Consumer<List<TradeOffers.Factory>> factory) {
final List<TradeOffers.Factory> list = new ArrayList<>();
factory.accept(list);
final TradeOffers.Factory[] additionalEntries = list.toArray(new TradeOffers.Factory[0]);
final Int2ObjectMap<TradeOffers.Factory[]> professionEntry = VILLAGER_TRADE_FACTORIES.computeIfAbsent(profession, p -> new Int2ObjectOpenHashMap<>());
final TradeOffers.Factory[] currentEntries = professionEntry.computeIfAbsent(level, l -> new TradeOffers.Factory[0]);
final TradeOffers.Factory[] newEntries = ArrayUtils.addAll(additionalEntries, currentEntries);
professionEntry.put(level, newEntries);
// Refresh the trades map
TradeOfferInternals.refreshOffers();
}
public static void registerWanderingTraderOffers(int level, Consumer<List<TradeOffers.Factory>> factory) {
final List<TradeOffers.Factory> list = new ArrayList<>();
factory.accept(list);
final TradeOffers.Factory[] additionalEntries = list.toArray(new TradeOffers.Factory[0]);
final TradeOffers.Factory[] currentEntries = TradeOfferInternals.DEFAULT_WANDERING_TRADER_OFFERS.computeIfAbsent(level, key -> new TradeOffers.Factory[0]);
// Merge current and new entries
final TradeOffers.Factory[] newEntries = ArrayUtils.addAll(additionalEntries, currentEntries);
TradeOfferInternals.DEFAULT_WANDERING_TRADER_OFFERS.put(level, newEntries);
// Refresh the trades map
TradeOfferInternals.refreshOffers();
}
public static void refreshOffers() {
TradeOfferInternals.refreshVillagerOffers();
TradeOfferInternals.refreshWanderingTraderOffers();
}
private static void refreshVillagerOffers() {
final HashMap<VillagerProfession, Int2ObjectMap<TradeOffers.Factory[]>> trades = new HashMap<>(TradeOfferInternals.DEFAULT_VILLAGER_OFFERS);
for (Map.Entry<VillagerProfession, Int2ObjectMap<TradeOffers.Factory[]>> tradeFactoryEntry : TradeOfferInternals.VILLAGER_TRADE_FACTORIES.entrySet()) {
// Create an empty map or get all existing profession entries.
final Int2ObjectMap<TradeOffers.Factory[]> leveledFactoryMap = trades.computeIfAbsent(tradeFactoryEntry.getKey(), k -> new Int2ObjectOpenHashMap<>());
// Get the existing entries
final Int2ObjectMap<TradeOffers.Factory[]> value = tradeFactoryEntry.getValue();
// Iterate through the existing level entries
for (int level : value.keySet()) {
final TradeOffers.Factory[] factories = value.get(level);
if (factories != null) {
final Int2ObjectMap<TradeOffers.Factory[]> resultMap = trades.computeIfAbsent(tradeFactoryEntry.getKey(), key -> new Int2ObjectOpenHashMap<>());
resultMap.put(level, ArrayUtils.addAll(leveledFactoryMap.computeIfAbsent(level, key -> new TradeOffers.Factory[0]), factories));
}
}
}
// Set the new villager trade map
TradeOffersAccessor.setVillagerTradeMap(trades);
}
private static void refreshWanderingTraderOffers() {
// Create an empty map that is a clone of the default offers
final Int2ObjectMap<TradeOffers.Factory[]> trades = new Int2ObjectOpenHashMap<>(TradeOfferInternals.DEFAULT_WANDERING_TRADER_OFFERS);
for (int level : TradeOfferInternals.WANDERING_TRADER_FACTORIES.keySet()) {
// Get all registered offers and add them to current entries
final TradeOffers.Factory[] factories = TradeOfferInternals.WANDERING_TRADER_FACTORIES.get(level);
trades.put(level, ArrayUtils.addAll(factories, trades.computeIfAbsent(level, key -> new TradeOffers.Factory[0])));
}
// Set the new wandering trader trade map
TradeOffersAccessor.setWanderingTraderTradeMap(trades);
}
static {
// Load the trade offers class so the field is set.
TradeOffers.PROFESSION_TO_LEVELED_TRADE.getClass();
}
}

View file

@ -14,18 +14,18 @@
* limitations under the License.
*/
package net.fabricmc.fabric.mixin.biome;
package net.fabricmc.fabric.mixin.object.builder;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Invoker;
import net.minecraft.world.dimension.DimensionType;
import net.minecraft.world.gen.chunk.ChunkGenerator;
import net.minecraft.advancement.criterion.Criteria;
import net.minecraft.advancement.criterion.Criterion;
@Mixin(DimensionType.class)
public interface DimensionTypeAccessor {
@Invoker("createNetherGenerator")
static ChunkGenerator createNetherGenerator(long seed) {
throw new UnsupportedOperationException();
@Mixin(Criteria.class)
public interface CriteriaAccessor {
@Invoker
static <T extends Criterion<?>> T callRegister(T object) {
throw new AssertionError("Mixin dummy");
}
}

View file

@ -0,0 +1,39 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.mixin.object.builder;
import java.util.Map;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;
import net.minecraft.village.TradeOffers;
import net.minecraft.village.VillagerProfession;
@Mixin(TradeOffers.class)
public interface TradeOffersAccessor {
@Accessor("PROFESSION_TO_LEVELED_TRADE")
static void setVillagerTradeMap(Map<VillagerProfession, Int2ObjectMap<TradeOffers.Factory[]>> trades) {
throw new AssertionError("This should not happen!");
}
@Accessor("WANDERING_TRADER_TRADES")
static void setWanderingTraderTradeMap(Int2ObjectMap<TradeOffers.Factory[]> trades) {
throw new AssertionError("This should not happen!");
}
}

View file

@ -14,28 +14,32 @@
* limitations under the License.
*/
package net.fabricmc.fabric.mixin.biome;
package net.fabricmc.fabric.mixin.object.builder;
import java.util.Map;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import net.minecraft.server.dedicated.MinecraftDedicatedServer;
import net.minecraft.server.dedicated.ServerPropertiesLoader;
import net.minecraft.village.TradeOffers;
import net.minecraft.village.VillagerProfession;
import net.fabricmc.fabric.impl.biome.InternalBiomeUtils;
import net.fabricmc.fabric.impl.object.builder.TradeOfferInternals;
@Mixin(MinecraftDedicatedServer.class)
public class MixinMinecraftDedicatedServer {
@Mixin(TradeOffers.class)
public abstract class TradeOffersMixin {
@Shadow
@Final
private ServerPropertiesLoader propertiesLoader;
public static Map<VillagerProfession, Int2ObjectMap<TradeOffers.Factory[]>> PROFESSION_TO_LEVELED_TRADE;
@Shadow
@Final
public static Int2ObjectMap<TradeOffers.Factory[]> WANDERING_TRADER_TRADES;
@Inject(method = "setupServer", at = @At("HEAD"))
public void setupServer(CallbackInfoReturnable<Boolean> cir) {
InternalBiomeUtils.recreateChunkGenerators(propertiesLoader.getPropertiesHandler().generatorOptions);
static {
// Cache the original trade lists
TradeOfferInternals.DEFAULT_VILLAGER_OFFERS = PROFESSION_TO_LEVELED_TRADE;
TradeOfferInternals.DEFAULT_WANDERING_TRADER_OFFERS = WANDERING_TRADER_TRADES;
}
}

View file

@ -0,0 +1,56 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.mixin.object.builder;
import java.util.Random;
import java.util.stream.Stream;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.Redirect;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import org.spongepowered.asm.mixin.injection.callback.LocalCapture;
import net.minecraft.entity.Entity;
import net.minecraft.item.ItemStack;
import net.minecraft.util.registry.DefaultedRegistry;
import net.minecraft.village.TradeOffer;
@Mixin(targets = "net/minecraft/village/TradeOffers$TypeAwareBuyForOneEmeraldFactory")
public abstract class TypeAwareTradeMixin {
/**
* Vanilla will check the "VillagerType -> Item" map in the stream and throw an exception for villager types not specified in the map.
* This breaks any and all custom villager types.
* We want to prevent this default logic so modded villager types will work.
* So we return an empty stream so an exception is never thrown.
*/
@Redirect(method = "<init>", at = @At(value = "INVOKE", target = "Lnet/minecraft/util/registry/DefaultedRegistry;stream()Ljava/util/stream/Stream;"))
private <T> Stream<T> disableVanillaCheck(DefaultedRegistry<T> registry) {
return Stream.empty();
}
/**
* To prevent "item" -> "air" trades, if the result of a type aware trade is air, make sure no offer is created.
*/
@Inject(method = "create(Lnet/minecraft/entity/Entity;Ljava/util/Random;)Lnet/minecraft/village/TradeOffer;", at = @At(value = "NEW", target = "net/minecraft/village/TradeOffer"), locals = LocalCapture.CAPTURE_FAILEXCEPTION, cancellable = true)
private void failOnNullItem(Entity entity, Random random, CallbackInfoReturnable<TradeOffer> cir, ItemStack buyingItem) {
if (buyingItem.isEmpty()) { // Will return true for an "empty" item stack that had null passed in the ctor
cir.setReturnValue(null); // Return null to prevent creation of empty trades
}
}
}

View file

@ -16,6 +16,7 @@
package net.fabricmc.fabric.mixin.object.builder;
import org.jetbrains.annotations.Nullable;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Invoker;
import com.google.common.collect.ImmutableSet;
@ -29,7 +30,7 @@ import net.minecraft.world.poi.PointOfInterestType;
@Mixin(VillagerProfession.class)
public interface VillagerProfessionAccessor {
@Invoker("<init>")
static VillagerProfession create(String id, PointOfInterestType type, ImmutableSet<Item> gatherableItems, ImmutableSet<Block> secondaryJobSites, /* @Nullable */ SoundEvent soundEvent) {
static VillagerProfession create(String id, PointOfInterestType type, ImmutableSet<Item> gatherableItems, ImmutableSet<Block> secondaryJobSites, @Nullable SoundEvent soundEvent) {
throw new AssertionError("Untransformed accessor!");
}
}

View file

@ -6,12 +6,16 @@
"AbstractBlockAccessor",
"AbstractBlockSettingsAccessor",
"AbstractBlockSettingsMixin",
"CriteriaAccessor",
"DefaultAttributeRegistryAccessor",
"DefaultAttributeRegistryMixin",
"MaterialBuilderAccessor",
"MixinBlock",
"PointOfInterestTypeAccessor",
"SpawnRestrictionAccessor",
"TradeOffersAccessor",
"TradeOffersMixin",
"TypeAwareTradeMixin",
"VillagerProfessionAccessor"
],
"client": [

View file

@ -0,0 +1,46 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.test.object.builder;
import com.google.gson.JsonObject;
import net.minecraft.advancement.criterion.ImpossibleCriterion;
import net.minecraft.predicate.entity.AdvancementEntityPredicateDeserializer;
import net.minecraft.util.Identifier;
import net.fabricmc.fabric.api.object.builder.v1.advancement.CriterionRegistry;
public final class CriterionRegistryTest {
public static void init() {
CriterionRegistry.register(new CustomCriterion());
}
static class CustomCriterion extends ImpossibleCriterion {
static final Identifier ID = ObjectBuilderTestConstants.id("custom");
@Override
public Identifier getId() {
return ID;
}
@Override
public Conditions conditionsFromJson(JsonObject jsonObject, AdvancementEntityPredicateDeserializer advancementEntityPredicateDeserializer) {
ObjectBuilderTestConstants.LOGGER.info("Loading custom criterion in advancement!");
return super.conditionsFromJson(jsonObject, advancementEntityPredicateDeserializer);
}
}
}

View file

@ -14,24 +14,18 @@
* limitations under the License.
*/
package net.fabricmc.fabric.api.biomes.v1;
package net.fabricmc.fabric.test.object.builder;
import net.minecraft.world.biome.Biome;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import net.fabricmc.fabric.impl.biome.InternalBiomeData;
import net.minecraft.util.Identifier;
/**
* General API that applies to all biome sources.
*/
public final class FabricBiomes {
private FabricBiomes() { }
public final class ObjectBuilderTestConstants {
public static final String MOD_ID = "fabric-object-builder-api-v1-testmod";
public static final Logger LOGGER = LogManager.getLogger(MOD_ID);
/**
* Allows players to naturally spawn in this biome.
*
* @param biome a biome the player should be able to spawn in
*/
public static void addSpawnBiome(Biome biome) {
InternalBiomeData.addSpawnBiome(biome);
public static Identifier id(String name) {
return new Identifier(MOD_ID, name);
}
}

View file

@ -0,0 +1,37 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.test.object.builder;
import java.util.Random;
import net.minecraft.entity.Entity;
import net.minecraft.village.TradeOffer;
import net.minecraft.village.TradeOffers;
class SimpleTradeFactory implements TradeOffers.Factory {
private final TradeOffer offer;
SimpleTradeFactory(TradeOffer offer) {
this.offer = offer;
}
@Override
public TradeOffer create(Entity entity, Random random) {
// ALWAYS supply a copy of the offer.
return new TradeOffer(this.offer.toTag());
}
}

View file

@ -0,0 +1,86 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.test.object.builder;
import static net.minecraft.command.argument.EntityArgumentType.entity;
import static net.minecraft.command.argument.EntityArgumentType.getEntity;
import static net.minecraft.server.command.CommandManager.argument;
import static net.minecraft.server.command.CommandManager.literal;
import java.util.Random;
import com.mojang.brigadier.exceptions.SimpleCommandExceptionType;
import net.minecraft.entity.Entity;
import net.minecraft.entity.passive.WanderingTraderEntity;
import net.minecraft.item.ItemStack;
import net.minecraft.item.Items;
import net.minecraft.text.LiteralText;
import net.minecraft.village.TradeOffer;
import net.minecraft.village.TradeOffers;
import net.minecraft.village.VillagerProfession;
import net.fabricmc.api.ModInitializer;
import net.fabricmc.fabric.api.command.v1.CommandRegistrationCallback;
import net.fabricmc.fabric.api.object.builder.v1.trade.TradeOfferHelper;
public class VillagerTypeTest1 implements ModInitializer {
@Override
public void onInitialize() {
TradeOfferHelper.registerVillagerOffers(VillagerProfession.ARMORER, 1, factories -> {
factories.add(new SimpleTradeFactory(new TradeOffer(new ItemStack(Items.GOLD_INGOT, 3), new ItemStack(Items.NETHERITE_SCRAP, 4), new ItemStack(Items.NETHERITE_INGOT), 2, 6, 0.15F)));
});
TradeOfferHelper.registerWanderingTraderOffers(1, factories -> {
factories.add(new SimpleTradeFactory(new TradeOffer(new ItemStack(Items.GOLD_INGOT, 3), new ItemStack(Items.NETHERITE_SCRAP, 4), new ItemStack(Items.NETHERITE_INGOT), 2, 6, 0.35F)));
});
CommandRegistrationCallback.EVENT.register((dispatcher, dedicated) -> {
dispatcher.register(literal("fabric_refreshtrades").executes(context -> {
TradeOfferHelper.refreshOffers();
context.getSource().sendFeedback(new LiteralText("Refreshed trades"), false);
return 1;
}));
dispatcher.register(literal("fabric_applywandering_trades")
.then(argument("entity", entity()).executes(context -> {
final Entity entity = getEntity(context, "entity");
if (!(entity instanceof WanderingTraderEntity)) {
throw new SimpleCommandExceptionType(new LiteralText("Entity is not a wandering trader")).create();
}
WanderingTraderEntity trader = (WanderingTraderEntity) entity;
trader.getOffers().clear();
for (TradeOffers.Factory[] value : TradeOffers.WANDERING_TRADER_TRADES.values()) {
for (TradeOffers.Factory factory : value) {
final TradeOffer result = factory.create(trader, new Random());
if (result == null) {
continue;
}
trader.getOffers().add(result);
}
}
return 1;
})));
});
}
}

View file

@ -0,0 +1,37 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.test.object.builder;
import net.minecraft.item.ItemStack;
import net.minecraft.item.Items;
import net.minecraft.village.TradeOffer;
import net.minecraft.village.VillagerProfession;
import net.fabricmc.api.ModInitializer;
import net.fabricmc.fabric.api.object.builder.v1.trade.TradeOfferHelper;
/*
* Second entrypoint to validate class loading does not break this.
*/
public class VillagerTypeTest2 implements ModInitializer {
@Override
public void onInitialize() {
TradeOfferHelper.registerVillagerOffers(VillagerProfession.ARMORER, 1, factories -> {
factories.add(new SimpleTradeFactory(new TradeOffer(new ItemStack(Items.DIAMOND, 20), new ItemStack(Items.NETHERITE_INGOT), 3, 4, 0.15F)));
});
}
}

View file

@ -0,0 +1,28 @@
{
"display": {
"icon": {
"item": "minecraft:command_block_minecart"
},
"title": {
"text": "Criterion registry test advancement"
},
"description": {
"text": "Criterion registry test advancement description"
},
"frame": "task",
"show_toast": false,
"announce_to_chat": false,
"hidden": false,
"background": "minecraft:textures/gui/advancements/backgrounds/stone.png"
},
"criteria": {
"custom": {
"trigger": "fabric-object-builder-api-v1-testmod:custom"
}
},
"requirements": [
[
"custom"
]
]
}

View file

@ -0,0 +1,29 @@
{
"schemaVersion": 1,
"id": "fabric-object-builder-api-v1-testmod",
"name": "Fabric Object Builder API (v1) Test Mod",
"version": "${version}",
"environment": "*",
"license": "Apache-2.0",
"icon": "assets/fabric-object-builder-api-v1-testmod/icon.png",
"contact": {
"homepage": "https://fabricmc.net",
"irc": "irc://irc.esper.net:6667/fabric",
"issues": "https://github.com/FabricMC/fabric/issues",
"sources": "https://github.com/FabricMC/fabric"
},
"authors": [
"FabricMC"
],
"depends": {
"fabric-object-builder-api-v1": "*"
},
"description": "Test mod for fabric object builder API v1.",
"entrypoints": {
"main": [
"net.fabricmc.fabric.test.object.builder.CriterionRegistryTest::init",
"net.fabricmc.fabric.test.object.builder.VillagerTypeTest1",
"net.fabricmc.fabric.test.object.builder.VillagerTypeTest2"
]
}
}

View file

@ -1,5 +1,5 @@
archivesBaseName = "fabric-registry-sync-v0"
version = getSubprojectVersion(project, "0.4.5")
version = getSubprojectVersion(project, "0.5.2")
dependencies {
compile project(path: ':fabric-api-base', configuration: 'dev')

View file

@ -27,11 +27,6 @@ public interface RegistryEntryAddedCallback<T> {
void onEntryAdded(int rawId, Identifier id, T object);
static <T> Event<RegistryEntryAddedCallback<T>> event(Registry<T> registry) {
if (!(registry instanceof ListenableRegistry)) {
throw new IllegalArgumentException("Unsupported registry: " + registry.getClass().getName());
}
//noinspection unchecked
return (Event<RegistryEntryAddedCallback<T>>) ((ListenableRegistry) registry).fabric_getAddObjectEvent();
return ListenableRegistry.get(registry).fabric_getAddObjectEvent();
}
}

View file

@ -27,11 +27,6 @@ public interface RegistryEntryRemovedCallback<T> {
void onEntryRemoved(int rawId, Identifier id, T object);
static <T> Event<RegistryEntryRemovedCallback<T>> event(Registry<T> registry) {
if (!(registry instanceof ListenableRegistry)) {
throw new IllegalArgumentException("Unsupported registry: " + registry.getClass().getName());
}
//noinspection unchecked
return (Event<RegistryEntryRemovedCallback<T>>) ((ListenableRegistry) registry).fabric_getRemoveObjectEvent();
return ListenableRegistry.get(registry).fabric_getRemoveObjectEvent();
}
}

View file

@ -49,11 +49,6 @@ public interface RegistryIdRemapCallback<T> {
}
static <T> Event<RegistryIdRemapCallback<T>> event(Registry<T> registry) {
if (!(registry instanceof ListenableRegistry)) {
throw new IllegalArgumentException("Unsupported registry: " + registry.getClass().getName());
}
//noinspection unchecked
return ((ListenableRegistry) registry).fabric_getRemapEvent();
return ListenableRegistry.get(registry).fabric_getRemapEvent();
}
}

View file

@ -0,0 +1,66 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.impl.registry.sync;
import com.mojang.serialization.Lifecycle;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import net.minecraft.util.registry.BuiltinRegistries;
import net.minecraft.util.registry.DynamicRegistryManager;
import net.minecraft.util.registry.MutableRegistry;
import net.minecraft.util.registry.Registry;
import net.minecraft.util.registry.RegistryKey;
import net.fabricmc.fabric.api.event.registry.RegistryEntryAddedCallback;
import net.fabricmc.fabric.mixin.registry.sync.AccessorRegistry;
/**
* Handles synchronising changes to the built-in registries into the dynamic registry manager's template manager,
* in case it gets classloaded early.
*/
public class DynamicRegistrySync {
private static final Logger LOGGER = LogManager.getLogger();
/**
* Sets up a synchronisation that will propagate added entries to the given dynamic registry manager, which
* should be the <em>built-in</em> manager. It is never destroyed. We don't ever have to unregister
* the registry events.
*/
public static void setupSync(DynamicRegistryManager.Impl template) {
LOGGER.debug("Setting up synchronisation of new BuiltinRegistries entries to the built-in DynamicRegistryManager");
BuiltinRegistries.REGISTRIES.stream().forEach(source -> setupSync(source, template));
}
/**
* Sets up an event registration for the source registy that will ensure all entries added from now on
* are also added to the template for dynamic registry managers.
*/
private static <T> void setupSync(Registry<T> source, DynamicRegistryManager.Impl template) {
@SuppressWarnings("unchecked") AccessorRegistry<T> sourceAccessor = (AccessorRegistry<T>) source;
RegistryKey<? extends Registry<T>> sourceKey = source.getKey();
MutableRegistry<T> target = template.get(sourceKey);
RegistryEntryAddedCallback.event(source).register((rawId, id, object) -> {
LOGGER.trace("Synchronizing {} from built-in registry {} into built-in dynamic registry manager template.",
id, source.getKey());
Lifecycle lifecycle = sourceAccessor.callGetEntryLifecycle(object);
RegistryKey<T> entryKey = RegistryKey.of(sourceKey, id);
target.set(rawId, entryKey, object, lifecycle);
});
}
}

View file

@ -16,6 +16,8 @@
package net.fabricmc.fabric.impl.registry.sync;
import net.minecraft.util.registry.Registry;
import net.fabricmc.fabric.api.event.Event;
import net.fabricmc.fabric.api.event.registry.RegistryEntryAddedCallback;
import net.fabricmc.fabric.api.event.registry.RegistryEntryRemovedCallback;
@ -25,4 +27,13 @@ public interface ListenableRegistry<T> {
Event<RegistryEntryAddedCallback<T>> fabric_getAddObjectEvent();
Event<RegistryEntryRemovedCallback<T>> fabric_getRemoveObjectEvent();
Event<RegistryIdRemapCallback<T>> fabric_getRemapEvent();
@SuppressWarnings("unchecked")
static <T> ListenableRegistry<T> get(Registry<T> registry) {
if (!(registry instanceof ListenableRegistry)) {
throw new IllegalArgumentException("Unsupported registry: " + registry.getKey().getValue());
}
// Safe cast: this is implemented via Mixin and T will always match the T in Registry<T>
return (ListenableRegistry<T>) registry;
}
}

View file

@ -35,6 +35,7 @@ import it.unimi.dsi.fastutil.objects.Object2IntMap;
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.Nullable;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.Packet;
@ -105,7 +106,8 @@ public final class RegistrySyncManager {
* @param activeTag contains the registry ids that were previously read and applied, can be null.
* @return a {@link CompoundTag} to save or sync, null when empty
*/
public static CompoundTag toTag(boolean isClientSync, CompoundTag activeTag) {
@Nullable
public static CompoundTag toTag(boolean isClientSync, @Nullable CompoundTag activeTag) {
CompoundTag mainTag = new CompoundTag();
for (Identifier registryId : Registry.REGISTRIES.getIds()) {

View file

@ -70,7 +70,7 @@ public final class StateIdTracker<T, S> implements RegistryIdRemapCallback<T>, R
}
private void recalcStateMap() {
((RemovableIdList) stateList).fabric_clear();
((RemovableIdList<?>) stateList).fabric_clear();
Int2ObjectMap<T> sortedBlocks = new Int2ObjectRBTreeMap<>();

View file

@ -16,8 +16,10 @@
package net.fabricmc.fabric.mixin.registry.sync;
import com.mojang.serialization.Lifecycle;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;
import org.spongepowered.asm.mixin.gen.Invoker;
import net.minecraft.util.registry.MutableRegistry;
import net.minecraft.util.registry.Registry;
@ -32,4 +34,7 @@ public interface AccessorRegistry<T> {
@Accessor()
RegistryKey<Registry<T>> getRegistryKey();
@Invoker
Lifecycle callGetEntryLifecycle(T object);
}

View file

@ -37,7 +37,6 @@ import net.fabricmc.fabric.impl.registry.sync.trackers.vanilla.BlockItemTracker;
@Mixin(Bootstrap.class)
public class MixinBootstrap {
@SuppressWarnings("unchecked")
@Inject(method = "setOutputStreams", at = @At("RETURN"))
private static void initialize(CallbackInfo info) {
// These seemingly pointless accesses are done to make sure each

View file

@ -0,0 +1,45 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.mixin.registry.sync;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import net.minecraft.util.registry.DynamicRegistryManager;
import net.fabricmc.fabric.impl.registry.sync.DynamicRegistrySync;
@Mixin(DynamicRegistryManager.class)
public class MixinDynamicRegistryManager {
// This is the "template" for all subsequent built-in dynamic registry managers,
// but it still contains the same objects as BuiltinRegistries, while the subsequent
// managers built from this template will contain copies.
@Shadow
private static DynamicRegistryManager.Impl BUILTIN;
/**
* Ensures that any registrations made into {@link net.minecraft.util.registry.BuiltinRegistries} after
* {@link DynamicRegistryManager} has been class-loaded are still propagated.
*/
@Inject(method = "<clinit>", at = @At(value = "TAIL"))
private static void setupBuiltInSync(CallbackInfo ci) {
DynamicRegistrySync.setupSync(BUILTIN);
}
}

View file

@ -31,13 +31,13 @@ import net.minecraft.util.collection.IdList;
import net.fabricmc.fabric.impl.registry.sync.RemovableIdList;
@Mixin(IdList.class)
public class MixinIdList implements RemovableIdList<Object> {
public class MixinIdList<T> implements RemovableIdList<T> {
@Shadow
private int nextId;
@Shadow
private IdentityHashMap<Object, Integer> idMap;
private IdentityHashMap<T, Integer> idMap;
@Shadow
private List<Object> list;
private List<T> list;
@Override
public void fabric_clear() {
@ -47,7 +47,7 @@ public class MixinIdList implements RemovableIdList<Object> {
}
@Unique
private void fabric_removeInner(Object o) {
private void fabric_removeInner(T o) {
int value = idMap.remove(o);
list.set(value, null);
@ -57,7 +57,7 @@ public class MixinIdList implements RemovableIdList<Object> {
}
@Override
public void fabric_remove(Object o) {
public void fabric_remove(T o) {
if (idMap.containsKey(o)) {
fabric_removeInner(o);
}
@ -65,9 +65,9 @@ public class MixinIdList implements RemovableIdList<Object> {
@Override
public void fabric_removeId(int i) {
List<Object> removals = new ArrayList<>();
List<T> removals = new ArrayList<>();
for (Object o : idMap.keySet()) {
for (T o : idMap.keySet()) {
int j = idMap.get(o);
if (i == j) {
@ -86,15 +86,15 @@ public class MixinIdList implements RemovableIdList<Object> {
@Override
public void fabric_remapIds(Int2IntMap map) {
// remap idMap
idMap.replaceAll((a, b) -> map.get(b));
idMap.replaceAll((a, b) -> map.get((int) b));
// remap list
nextId = 0;
List<Object> oldList = new ArrayList<>(list);
List<T> oldList = new ArrayList<>(list);
list.clear();
for (int k = 0; k < oldList.size(); k++) {
Object o = oldList.get(k);
T o = oldList.get(k);
if (o != null) {
int i = map.getOrDefault(k, k);

View file

@ -59,7 +59,7 @@ import net.fabricmc.fabric.impl.registry.sync.RemapStateImpl;
import net.fabricmc.fabric.impl.registry.sync.RemappableRegistry;
@Mixin(SimpleRegistry.class)
public abstract class MixinIdRegistry<T> implements RemappableRegistry, ListenableRegistry {
public abstract class MixinIdRegistry<T> extends Registry<T> implements RemappableRegistry, ListenableRegistry<T> {
@Shadow
@Final
private ObjectList<T> rawIdToEntry;
@ -77,31 +77,32 @@ public abstract class MixinIdRegistry<T> implements RemappableRegistry, Listenab
@Unique
private static Logger FABRIC_LOGGER = LogManager.getLogger();
public MixinIdRegistry(RegistryKey<? extends Registry<T>> key, Lifecycle lifecycle) {
super(key, lifecycle);
}
@Unique
private final Event<RegistryEntryAddedCallback> fabric_addObjectEvent = EventFactory.createArrayBacked(RegistryEntryAddedCallback.class,
private final Event<RegistryEntryAddedCallback<T>> fabric_addObjectEvent = EventFactory.createArrayBacked(RegistryEntryAddedCallback.class,
(callbacks) -> (rawId, id, object) -> {
for (RegistryEntryAddedCallback callback : callbacks) {
//noinspection unchecked
for (RegistryEntryAddedCallback<T> callback : callbacks) {
callback.onEntryAdded(rawId, id, object);
}
}
);
@Unique
private final Event<RegistryEntryRemovedCallback> fabric_removeObjectEvent = EventFactory.createArrayBacked(RegistryEntryRemovedCallback.class,
private final Event<RegistryEntryRemovedCallback<T>> fabric_removeObjectEvent = EventFactory.createArrayBacked(RegistryEntryRemovedCallback.class,
(callbacks) -> (rawId, id, object) -> {
for (RegistryEntryRemovedCallback callback : callbacks) {
//noinspection unchecked
for (RegistryEntryRemovedCallback<T> callback : callbacks) {
callback.onEntryRemoved(rawId, id, object);
}
}
);
@Unique
private final Event<RegistryIdRemapCallback> fabric_postRemapEvent = EventFactory.createArrayBacked(RegistryIdRemapCallback.class,
private final Event<RegistryIdRemapCallback<T>> fabric_postRemapEvent = EventFactory.createArrayBacked(RegistryIdRemapCallback.class,
(callbacks) -> (a) -> {
for (RegistryIdRemapCallback callback : callbacks) {
//noinspection unchecked
for (RegistryIdRemapCallback<T> callback : callbacks) {
callback.onRemap(a);
}
}
@ -114,20 +115,17 @@ public abstract class MixinIdRegistry<T> implements RemappableRegistry, Listenab
@Override
public Event<RegistryEntryAddedCallback<T>> fabric_getAddObjectEvent() {
//noinspection unchecked
return (Event) fabric_addObjectEvent;
return fabric_addObjectEvent;
}
@Override
public Event<RegistryEntryRemovedCallback<T>> fabric_getRemoveObjectEvent() {
//noinspection unchecked
return (Event) fabric_removeObjectEvent;
return fabric_removeObjectEvent;
}
@Override
public Event<RegistryIdRemapCallback<T>> fabric_getRemapEvent() {
//noinspection unchecked
return (Event) fabric_postRemapEvent;
return fabric_postRemapEvent;
}
// The rest of the registry isn't thread-safe, so this one need not be either.
@ -136,8 +134,8 @@ public abstract class MixinIdRegistry<T> implements RemappableRegistry, Listenab
@SuppressWarnings({"unchecked", "ConstantConditions"})
@Inject(method = "set(ILnet/minecraft/util/registry/RegistryKey;Ljava/lang/Object;Lcom/mojang/serialization/Lifecycle;Z)Ljava/lang/Object;", at = @At("HEAD"))
public void setPre(int id, RegistryKey<T> registryId, Object object, Lifecycle lifecycle, boolean checkDuplicateKeys, CallbackInfoReturnable info) {
int indexedEntriesId = entryToRawId.getInt((T) object);
public void setPre(int id, RegistryKey<T> registryId, T object, Lifecycle lifecycle, boolean checkDuplicateKeys, CallbackInfoReturnable info) {
int indexedEntriesId = entryToRawId.getInt(object);
if (indexedEntriesId >= 0) {
throw new RuntimeException("Attempted to register object " + object + " twice! (at raw IDs " + indexedEntriesId + " and " + id + " )");
@ -165,7 +163,7 @@ public abstract class MixinIdRegistry<T> implements RemappableRegistry, Listenab
@SuppressWarnings("unchecked")
@Inject(method = "set(ILnet/minecraft/util/registry/RegistryKey;Ljava/lang/Object;Lcom/mojang/serialization/Lifecycle;Z)Ljava/lang/Object;", at = @At("RETURN"))
public void setPost(int id, RegistryKey<T> registryId, Object object, Lifecycle lifecycle, boolean checkDuplicateKeys, CallbackInfoReturnable info) {
public void setPost(int id, RegistryKey<T> registryId, T object, Lifecycle lifecycle, boolean checkDuplicateKeys, CallbackInfoReturnable info) {
if (fabric_isObjectNew) {
fabric_addObjectEvent.invoker().onEntryAdded(id, registryId.getValue(), object);
}
@ -173,9 +171,6 @@ public abstract class MixinIdRegistry<T> implements RemappableRegistry, Listenab
@Override
public void remap(String name, Object2IntMap<Identifier> remoteIndexedEntries, RemapMode mode) throws RemapException {
//noinspection unchecked, ConstantConditions
SimpleRegistry<Object> registry = (SimpleRegistry<Object>) (Object) this;
// Throw on invalid conditions.
switch (mode) {
case AUTHORITATIVE:
@ -184,7 +179,7 @@ public abstract class MixinIdRegistry<T> implements RemappableRegistry, Listenab
List<String> strings = null;
for (Identifier remoteId : remoteIndexedEntries.keySet()) {
if (!idToEntry.keySet().contains(remoteId)) {
if (!idToEntry.containsKey(remoteId)) {
if (strings == null) {
strings = new ArrayList<>();
}
@ -210,13 +205,13 @@ public abstract class MixinIdRegistry<T> implements RemappableRegistry, Listenab
List<String> strings = new ArrayList<>();
for (Identifier remoteId : remoteIndexedEntries.keySet()) {
if (!idToEntry.keySet().contains(remoteId)) {
if (!idToEntry.containsKey(remoteId)) {
strings.add(" - " + remoteId + " (missing on local)");
}
}
for (Identifier localId : registry.getIds()) {
if (!remoteIndexedEntries.keySet().contains(localId)) {
for (Identifier localId : getIds()) {
if (!remoteIndexedEntries.containsKey(localId)) {
strings.add(" - " + localId + " (missing on remote)");
}
}
@ -244,15 +239,15 @@ public abstract class MixinIdRegistry<T> implements RemappableRegistry, Listenab
fabric_prevIndexedEntries = new Object2IntOpenHashMap<>();
fabric_prevEntries = HashBiMap.create(idToEntry);
for (Object o : registry) {
fabric_prevIndexedEntries.put(registry.getId(o), registry.getRawId(o));
for (T o : this) {
fabric_prevIndexedEntries.put(getId(o), getRawId(o));
}
}
Int2ObjectMap<Identifier> oldIdMap = new Int2ObjectOpenHashMap<>();
for (Object o : registry) {
oldIdMap.put(registry.getRawId(o), registry.getId(o));
for (T o : this) {
oldIdMap.put(getRawId(o), getId(o));
}
// If we're AUTHORITATIVE, we append entries which only exist on the
@ -270,7 +265,7 @@ public abstract class MixinIdRegistry<T> implements RemappableRegistry, Listenab
if (v > maxValue) maxValue = v;
}
for (Identifier id : registry.getIds()) {
for (Identifier id : getIds()) {
if (!remoteIndexedEntries.containsKey(id)) {
FABRIC_LOGGER.warn("Adding " + id + " to saved/remote registry.");
remoteIndexedEntries.put(id, ++maxValue);
@ -283,16 +278,15 @@ public abstract class MixinIdRegistry<T> implements RemappableRegistry, Listenab
// TODO: Is this what mods really want?
Set<Identifier> droppedIds = new HashSet<>();
for (Identifier id : registry.getIds()) {
for (Identifier id : getIds()) {
if (!remoteIndexedEntries.containsKey(id)) {
Object object = registry.get(id);
int rid = registry.getRawId(object);
T object = get(id);
int rid = getRawId(object);
droppedIds.add(id);
// Emit RemoveObject events for removed objects.
//noinspection unchecked
fabric_getRemoveObjectEvent().invoker().onEntryRemoved(rid, id, (T) object);
fabric_getRemoveObjectEvent().invoker().onEntryRemoved(rid, id, object);
}
}
@ -306,9 +300,9 @@ public abstract class MixinIdRegistry<T> implements RemappableRegistry, Listenab
Int2IntMap idMap = new Int2IntOpenHashMap();
for (Object o : rawIdToEntry) {
Identifier id = registry.getId(o);
int rid = registry.getRawId(o);
for (T o : rawIdToEntry) {
Identifier id = getId(o);
int rid = getRawId(o);
// see above note
if (remoteIndexedEntries.containsKey(id)) {
@ -351,8 +345,7 @@ public abstract class MixinIdRegistry<T> implements RemappableRegistry, Listenab
}
}
//noinspection unchecked
fabric_getRemapEvent().invoker().onRemap(new RemapStateImpl(registry, oldIdMap, idMap));
fabric_getRemapEvent().invoker().onRemap(new RemapStateImpl<>(this, oldIdMap, idMap));
}
@Override
@ -374,8 +367,8 @@ public abstract class MixinIdRegistry<T> implements RemappableRegistry, Listenab
idToEntry.putAll(fabric_prevEntries);
for (Map.Entry<Identifier, T> entry : fabric_prevEntries.entrySet()) {
//noinspection unchecked
keyToEntry.put(RegistryKey.of(RegistryKey.ofRegistry(((Registry) Registry.REGISTRIES).getId(this)), entry.getKey()), entry.getValue());
RegistryKey<T> entryKey = RegistryKey.of(getKey(), entry.getKey());
keyToEntry.put(entryKey, entry.getValue());
}
remap(name, fabric_prevIndexedEntries, RemapMode.AUTHORITATIVE);

View file

@ -25,6 +25,7 @@ import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import net.minecraft.util.Identifier;
import net.minecraft.util.registry.Registry;
import net.minecraft.util.registry.SimpleRegistry;
import net.minecraft.util.registry.RegistryKey;
@ -58,8 +59,8 @@ public abstract class MixinSimpleRegistry<T> extends Registry<T> {
RegistryAttributeHolder holder = RegistryAttributeHolder.get(this);
if (!holder.hasAttribute(RegistryAttribute.MODDED)) {
// noinspection unchecked
FARBIC_LOGGER.debug("Registry {} has been marked as modded, registry entry {} was changed", ((Registry) Registry.REGISTRIES).getId(this), registryKey.getValue());
Identifier id = getKey().getValue();
FARBIC_LOGGER.debug("Registry {} has been marked as modded, registry entry {} was changed", id, registryKey.getValue());
RegistryAttributeHolder.get(this).addAttribute(RegistryAttribute.MODDED);
}
}

View file

@ -5,6 +5,7 @@
"mixins": [
"AccessorRegistry",
"MixinBootstrap",
"MixinDynamicRegistryManager",
"MixinIdList",
"MixinIdRegistry",
"MixinPlayerManager",

View file

@ -24,8 +24,14 @@ import net.minecraft.block.Material;
import net.minecraft.item.BlockItem;
import net.minecraft.item.Item;
import net.minecraft.util.Identifier;
import net.minecraft.util.registry.BuiltinRegistries;
import net.minecraft.util.registry.DynamicRegistryManager;
import net.minecraft.util.registry.MutableRegistry;
import net.minecraft.util.registry.Registry;
import net.minecraft.util.registry.SimpleRegistry;
import net.minecraft.world.gen.feature.ConfiguredFeature;
import net.minecraft.world.gen.feature.DefaultFeatureConfig;
import net.minecraft.world.gen.feature.Feature;
import net.fabricmc.api.ModInitializer;
import net.fabricmc.fabric.api.event.registry.DynamicRegistrySetupCallback;
@ -43,6 +49,8 @@ public class RegistrySyncTest implements ModInitializer {
@Override
public void onInitialize() {
testBuiltInRegistrySync();
if (REGISTER_BLOCKS) {
for (int i = 0; i < 5; i++) {
Block block = new Block(AbstractBlock.Settings.of(Material.STONE));
@ -79,4 +87,53 @@ public class RegistrySyncTest implements ModInitializer {
});
});
}
/**
* Tests that built-in registries are properly synchronized even after the dynamic reigstry managers have been
* class-loaded.
*/
private void testBuiltInRegistrySync() {
System.out.println("Checking built-in registry sync...");
// Register a configured feature before force-loading the dynamic registry manager
ConfiguredFeature<DefaultFeatureConfig, ?> cf1 = Feature.BASALT_PILLAR.configure(DefaultFeatureConfig.INSTANCE);
Identifier f1Id = new Identifier("registry_sync", "f1");
Registry.register(BuiltinRegistries.CONFIGURED_FEATURE, f1Id, cf1);
// Force-Initialize the dynamic registry manager, doing this in a Mod initializer would cause
// further registrations into BuiltInRegistries to _NOT_ propagate into DynamicRegistryManager.BUILTIN
checkFeature(DynamicRegistryManager.create(), f1Id);
ConfiguredFeature<DefaultFeatureConfig, ?> cf2 = Feature.DESERT_WELL.configure(DefaultFeatureConfig.INSTANCE);
Identifier f2Id = new Identifier("registry_sync", "f2");
Registry.register(BuiltinRegistries.CONFIGURED_FEATURE, f2Id, cf2);
DynamicRegistryManager.Impl impl2 = DynamicRegistryManager.create();
checkFeature(impl2, f1Id);
checkFeature(impl2, f2Id);
}
private void checkFeature(DynamicRegistryManager manager, Identifier id) {
MutableRegistry<ConfiguredFeature<?, ?>> registry = manager.get(Registry.CONFIGURED_FEATURE_WORLDGEN);
ConfiguredFeature<?, ?> builtInEntry = BuiltinRegistries.CONFIGURED_FEATURE.get(id);
if (builtInEntry == null) {
throw new IllegalStateException("Expected built-in entry to exist for: " + id);
}
ConfiguredFeature<?, ?> entry = registry.get(id);
if (entry == null) {
throw new IllegalStateException("Expected dynamic registry to contain entry " + id);
}
if (builtInEntry == entry) {
throw new IllegalStateException("Expected that the built-in entry and dynamic entry don't have object identity because the dynamic entry is created by serializing the built-in entry to JSON and back.");
}
if (builtInEntry.feature != entry.feature) {
throw new IllegalStateException("Expected both entries to reference the same feature since it's only in Registry and is never copied");
}
}
}

View file

@ -1,5 +1,5 @@
archivesBaseName = "fabric-renderer-api-v1"
version = getSubprojectVersion(project, "0.3.0")
version = getSubprojectVersion(project, "0.3.1")
dependencies {
compile project(path: ':fabric-api-base', configuration: 'dev')

View file

@ -16,6 +16,8 @@
package net.fabricmc.fabric.api.renderer.v1;
import org.jetbrains.annotations.Nullable;
import net.minecraft.util.Identifier;
import net.fabricmc.fabric.api.renderer.v1.material.MaterialFinder;
@ -50,6 +52,7 @@ public interface Renderer {
* Return a material previously registered via {@link #registerMaterial(Identifier, RenderMaterial)}.
* Will return null if no material was found matching the given identifier.
*/
@Nullable
RenderMaterial materialById(Identifier id);
/**

View file

@ -16,6 +16,8 @@
package net.fabricmc.fabric.api.renderer.v1;
import org.jetbrains.annotations.Nullable;
import net.fabricmc.fabric.impl.renderer.RendererAccessImpl;
/**
@ -37,6 +39,7 @@ public interface RendererAccess {
* Access to the current {@link Renderer} for creating and retrieving model builders
* and materials. Will return null if no render plug in is active.
*/
@Nullable
Renderer getRenderer();
/**

View file

@ -16,6 +16,8 @@
package net.fabricmc.fabric.api.renderer.v1.mesh;
import org.jetbrains.annotations.Nullable;
import net.minecraft.client.render.model.BakedQuad;
import net.minecraft.client.texture.Sprite;
import net.minecraft.client.util.math.Vector3f;
@ -115,7 +117,8 @@ public interface MutableQuadView extends QuadView {
* is computed based on face geometry and must be non-null in vanilla quads.
* That computed value is returned by {@link #lightFace()}.
*/
MutableQuadView cullFace(Direction face);
@Nullable
MutableQuadView cullFace(@Nullable Direction face);
/**
* Provides a hint to renderer about the facing of this quad. Not required,
@ -130,6 +133,7 @@ public interface MutableQuadView extends QuadView {
* <p>Note: This value is not persisted independently when the quad is encoded.
* When reading encoded quads, this value will always be the same as {@link #lightFace()}.
*/
@Nullable
MutableQuadView nominalFace(Direction face);
/**

View file

@ -16,6 +16,9 @@
package net.fabricmc.fabric.api.renderer.v1.mesh;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import net.minecraft.client.render.VertexFormats;
import net.minecraft.client.render.model.BakedQuad;
import net.minecraft.client.texture.Sprite;
@ -77,6 +80,7 @@ public interface QuadView {
* calculations and will be the block face to which the quad is most closely aligned. Always
* the same as cull face for quads that are on a block face, but never null.
*/
@NotNull
Direction lightFace();
/**
@ -85,7 +89,7 @@ public interface QuadView {
*
* @see MutableQuadView#cullFace(Direction)
*/
Direction cullFace();
@Nullable Direction cullFace();
/**
* See {@link MutableQuadView#nominalFace(Direction)}.
@ -135,7 +139,7 @@ public interface QuadView {
* Pass a non-null target to avoid allocation - will be returned with values.
* Otherwise returns a new instance.
*/
Vector3f copyPos(int vertexIndex, Vector3f target);
Vector3f copyPos(int vertexIndex, @Nullable Vector3f target);
/**
* Convenience: access x, y, z by index 0-2.
@ -167,7 +171,8 @@ public interface QuadView {
* Pass a non-null target to avoid allocation - will be returned with values.
* Otherwise returns a new instance. Returns null if normal not present.
*/
Vector3f copyNormal(int vertexIndex, Vector3f target);
@Nullable
Vector3f copyNormal(int vertexIndex, @Nullable Vector3f target);
/**
* Will return {@link Float#NaN} if normal not present.

View file

@ -20,6 +20,7 @@ import java.util.Arrays;
import java.util.List;
import com.google.common.collect.ImmutableList;
import org.jetbrains.annotations.Contract;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.render.model.BakedQuad;
@ -56,6 +57,7 @@ public abstract class ModelHelper {
* optionally including the null face. (Use &lt; or &lt;= {@link #NULL_FACE_ID}
* to exclude or include the null value, respectively.)
*/
@Contract("null -> null")
public static Direction faceFromIndex(int faceIndex) {
return FACES[faceIndex];
}

View file

@ -1,5 +1,5 @@
archivesBaseName = "fabric-renderer-indigo"
version = getSubprojectVersion(project, "0.4.0")
version = getSubprojectVersion(project, "0.4.1")
dependencies {
compile project(path: ':fabric-api-base', configuration: 'dev')

View file

@ -18,6 +18,8 @@ package net.fabricmc.fabric.impl.client.indigo.renderer.helper;
import static net.minecraft.util.math.MathHelper.approximatelyEquals;
import org.jetbrains.annotations.NotNull;
import net.minecraft.client.render.model.BakedQuad;
import net.minecraft.client.util.math.Vector3f;
import net.minecraft.util.math.Direction;
@ -114,7 +116,7 @@ public abstract class GeometryHelper {
*
* @param lightFace MUST be non-null.
*/
public static boolean isQuadCubic(Direction lightFace, QuadView quad) {
public static boolean isQuadCubic(@NotNull Direction lightFace, QuadView quad) {
if (lightFace == null) {
return false;
}

Some files were not shown because too many files have changed in this diff Show more