Merge branches 'pull/219', 'pull/300' and 'pull/301' into us/master

This commit is contained in:
modmuss50 2019-07-12 10:25:31 +01:00
commit ab421b9c5c
112 changed files with 3489 additions and 662 deletions

View file

@ -12,7 +12,7 @@ plugins {
def ENV = System.getenv()
def baseVersion = "0.3.0"
def mcVersion = "1.14.2"
def mcVersion = "1.14.3"
def yarnVersion = "+build.1"
def getSubprojectVersion(project, version) {
@ -182,7 +182,7 @@ curseforge {
project {
id = '306612'
changelog = 'A changelog can be found at https://github.com/FabricMC/fabric/commits/master'
releaseType = 'beta'
releaseType = 'alpha'
addGameVersion '1.14-Snapshot'
mainArtifact(file("${project.buildDir}/libs/${archivesBaseName}-${version}.jar")) {
displayName = "[$mcVersion] Fabric API $baseVersion build $ENV.BUILD_NUMBER"

View file

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

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.api.biomes.v1;
import net.fabricmc.fabric.impl.biomes.InternalBiomeData;
import net.minecraft.world.biome.Biome;
/**
* General API that applies to all biome sources
*/
public final class FabricBiomes {
private FabricBiomes() {
}
/**
* 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);
}
}

View file

@ -0,0 +1,111 @@
/*
* 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.biomes.v1;
import net.fabricmc.fabric.impl.biomes.InternalBiomeData;
import net.minecraft.world.biome.Biome;
/**
* API that exposes some internals of the minecraft default biome source for the overworld
*/
public final class 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 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.
* @see OverworldClimate for a list of vanilla biome weights
*/
public static void addContinentalBiome(Biome biome, OverworldClimate climate, double weight) {
InternalBiomeData.addOverworldContinentalBiome(climate, biome, weight);
}
/**
* 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 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.
*/
public static void addHillsBiome(Biome parent, Biome hills, double weight) {
InternalBiomeData.addOverworldHillsBiome(parent, hills, weight);
}
/**
* 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 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.
*/
public static void addShoreBiome(Biome parent, Biome shore, double weight) {
InternalBiomeData.addOverworldShoreBiome(parent, shore, weight);
}
/**
* 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 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.
*/
public static void addEdgeBiome(Biome parent, Biome edge, double weight) {
InternalBiomeData.addOverworldEdgeBiome(parent, edge, weight);
}
/**
* Adds a 'variant' biome which replaces another biome on occasion.
* For example, addBiomeVariant(Biomes.JUNGLE, Biomes.DESERT, 0.2) will replace 20% of jungles with deserts.
* This method is rather useful for replacing biomes not generated through standard methods, such as oceans,
* deep oceans, jungles, mushroom islands, etc. When replacing ocean and deep ocean biomes, one must specify
* the biome without temperature (Biomes.OCEAN / Biomes.DEEP_OCEAN) only, as ocean temperatures have not been
* assigned; additionally, one must not specify climates for oceans, deep oceans, or mushroom islands, as they do not have
* 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 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) {
InternalBiomeData.addOverworldBiomeReplacement(replaced, variant, chance, climates);
}
/**
* Sets the river type that will generate in the biome. If null is passed as the river biome, then rivers will not
* 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
*/
public static void setRiverBiome(Biome parent, Biome river) {
InternalBiomeData.setOverworldRiverBiome(parent, river);
}
}

View file

@ -0,0 +1,43 @@
/*
* 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.biomes.v1;
/**
* Represents the climates of biomes on the overworld continents
*/
public enum OverworldClimate {
/**
* Includes Snowy Tundra (with a weight of 3) and Snowy Taiga (with a weight of 1)
*/
SNOWY,
/**
* Includes Forest, Taiga, Mountains, and Plains (all with weights of 1)
*/
COOL,
/**
* Includes Forest, Dark Forest, Mountains, Plains, Birch Forest, and Swamp (all with weights of 1)
*/
TEMPERATE,
/**
* Includes Desert (with a weight of 3), Savanna (with a weight of 2), and Plains (with a weight of 1)
*/
DRY
}

View file

@ -0,0 +1,52 @@
/*
* 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.biomes;
import net.minecraft.world.biome.Biome;
/**
* Represents a biome variant and its corresponding chance
*/
final class BiomeVariant {
private final 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) {
this.variant = variant;
this.chance = chance;
}
/**
* @return the variant biome
*/
Biome getVariant() {
return variant;
}
/**
* @return the chance of replacement of the biome into the variant
*/
double getChance() {
return chance;
}
}

View file

@ -0,0 +1,54 @@
/*
* 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.biomes;
import net.minecraft.world.biome.Biome;
/**
* Represents a biome and its corresponding weight
*/
final class ContinentalBiomeEntry {
private final Biome biome;
private final double weight;
private final double upperWeightBound;
/**
* @param biome the biome
* @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) {
this.biome = biome;
this.weight = weight;
this.upperWeightBound = upperWeightBound;
}
Biome getBiome() {
return biome;
}
double getWeight() {
return weight;
}
/**
* @return the upper weight boundary for the search
*/
double getUpperWeightBound() {
return upperWeightBound;
}
}

View file

@ -0,0 +1,185 @@
/*
* 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.biomes;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableMap;
import net.fabricmc.fabric.api.biomes.v1.OverworldClimate;
import net.minecraft.util.registry.Registry;
import net.minecraft.world.biome.Biome;
import net.minecraft.world.biome.Biomes;
import net.minecraft.world.biome.layer.BiomeLayers;
import java.util.*;
/**
* 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 List<Biome> OVERWORLD_INJECTED_BIOMES = new ArrayList<>();
private static final Set<Biome> SPAWN_BIOMES = new HashSet<>();
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);
OVERWORLD_INJECTED_BIOMES.add(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);
OVERWORLD_INJECTED_BIOMES.add(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);
OVERWORLD_INJECTED_BIOMES.add(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);
OVERWORLD_INJECTED_BIOMES.add(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);
OVERWORLD_INJECTED_BIOMES.add(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) {
OVERWORLD_INJECTED_BIOMES.add(river);
}
}
public static void addSpawnBiome(Biome biome) {
Preconditions.checkArgument(biome != null, "Biome is null");
SPAWN_BIOMES.add(biome);
}
public static List<Biome> getOverworldInjectedBiomes() {
return OVERWORLD_INJECTED_BIOMES;
}
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;
}
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(Registry.BIOME.getRawId(base), Registry.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

@ -0,0 +1,144 @@
/*
* 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.biomes;
import net.fabricmc.fabric.api.biomes.v1.OverworldClimate;
import net.minecraft.util.Identifier;
import net.minecraft.util.registry.Registry;
import net.minecraft.world.biome.Biome;
import net.minecraft.world.biome.layer.LayerRandomnessSource;
import java.util.List;
import java.util.Map;
import java.util.function.IntConsumer;
/**
* Internal utilities used for biome sampling
*/
public final class InternalBiomeUtils {
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 center central biome that comparisons are relative to
* @return whether the central biome is an edge of a biome
*/
public static boolean isEdge(int north, int east, int south, int west, int center) {
return areUnsimilar(center, north) || areUnsimilar(center, east) || areUnsimilar(center, south) || areUnsimilar(center, west);
}
/**
* @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
*/
private static boolean areUnsimilar(int mainBiomeId, int secondaryBiomeId) {
if (mainBiomeId == secondaryBiomeId) { // for efficiency, determine if the ids are equal first
return false;
} else {
Biome secondaryBiome = Registry.BIOME.get(secondaryBiomeId);
Biome mainBiome = Registry.BIOME.get(mainBiomeId);
boolean isUnsimilar = secondaryBiome.hasParent() ? !(mainBiomeId == Registry.BIOME.getRawId(Registry.BIOME.get(new Identifier(secondaryBiome.getParent())))) : true;
isUnsimilar = isUnsimilar && (mainBiome.hasParent() ? !(secondaryBiomeId == Registry.BIOME.getRawId(Registry.BIOME.get(new Identifier(mainBiome.getParent())))) : true);
return isUnsimilar;
}
}
/**
* @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
* @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) {
return isOceanBiome(north) || isOceanBiome(east) || isOceanBiome(south) || isOceanBiome(west);
}
private static boolean isOceanBiome(int id) {
Biome biome = Registry.BIOME.get(id);
return biome != null && biome.getCategory() == Biome.Category.OCEAN;
}
public static int searchForBiome(double reqWeightSum, int vanillaArrayWeight, List<ContinentalBiomeEntry> moddedBiomes) {
reqWeightSum -= vanillaArrayWeight;
int low = 0;
int high = moddedBiomes.size() - 1;
while (low < high) {
int mid = (high + low) >>> 1;
if (reqWeightSum < moddedBiomes.get(mid).getUpperWeightBound()) {
high = mid;
} else {
low = mid + 1;
}
}
return low;
}
/**
* Potentially transforms a biome into its variants based on the provided 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
* @return The potentially transformed biome
*/
public static int transformBiome(LayerRandomnessSource random, Biome existing, OverworldClimate climate) {
Map<Biome, VariantTransformer> overworldVariantTransformers = InternalBiomeData.getOverworldVariantTransformers();
VariantTransformer transformer = overworldVariantTransformers.get(existing);
if (transformer != null) {
return Registry.BIOME.getRawId(transformer.transformBiome(existing, random, climate));
}
return Registry.BIOME.getRawId(existing);
}
public static void injectBiomesIntoClimate(LayerRandomnessSource random, int[] vanillaArray, OverworldClimate climate, IntConsumer result) {
WeightedBiomePicker picker = InternalBiomeData.getOverworldModdedContinentalBiomePickers().get(climate);
if (picker == null || picker.getCurrentWeightTotal() <= 0.0) {
// Return early, there are no modded biomes.
// Since we don't pass any values to the IntConsumer, this falls through to vanilla logic.
// Thus, this prevents Fabric from changing vanilla biome selection behavior without biome mods in this case.
return;
}
int vanillaArrayWeight = vanillaArray.length;
double reqWeightSum = (double) random.nextInt(Integer.MAX_VALUE) * (vanillaArray.length + picker.getCurrentWeightTotal()) / Integer.MAX_VALUE;
if (reqWeightSum < vanillaArray.length) {
// Vanilla biome; look it up from the vanilla array and transform accordingly.
result.accept(transformBiome(random, Registry.BIOME.get(vanillaArray[(int) reqWeightSum]), climate));
} else {
// Modded biome; use a binary search, and then transform accordingly.
ContinentalBiomeEntry found = picker.search(reqWeightSum - vanillaArrayWeight);
result.accept(transformBiome(random, found.getBiome(), climate));
}
}
}

View file

@ -0,0 +1,99 @@
/*
* 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.biomes;
import net.fabricmc.fabric.api.biomes.v1.OverworldClimate;
import net.minecraft.world.biome.Biome;
import net.minecraft.world.biome.layer.LayerRandomnessSource;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Deals with picking variants for you
*/
final class VariantTransformer {
private final SubTransformer defaultTransformer = new SubTransformer();
private final Map<OverworldClimate, SubTransformer> transformers = new HashMap<>();
/**
* @param variant the variant that the replaced biome is replaced with
* @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) {
if (climates == null || climates.length == 0) {
defaultTransformer.addBiome(variant, chance);
climates = OverworldClimate.values();
}
for (OverworldClimate climate : climates) {
transformers.computeIfAbsent(climate, c -> new SubTransformer()).addBiome(variant, chance);
}
}
/**
* Transforms a biome into a variant randomly depending on its chance
*
* @param replaced biome to transform
* @param random the {@link LayerRandomnessSource} from the layer
* @return the transformed biome
*/
Biome transformBiome(Biome replaced, LayerRandomnessSource random, OverworldClimate climate) {
if (climate == null) {
return defaultTransformer.transformBiome(replaced, random);
}
SubTransformer transformer = transformers.get(climate);
if (transformer != null) {
return transformer.transformBiome(replaced, random);
} else {
return replaced;
}
}
static final class SubTransformer {
private final List<BiomeVariant> variants = new ArrayList<>();
/**
* @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) {
variants.add(new BiomeVariant(variant, chance));
}
/**
* Transforms a biome into a variant randomly depending on its chance
*
* @param replaced biome to transform
* @param random the {@link LayerRandomnessSource} from the layer
* @return the transformed biome
*/
private Biome transformBiome(Biome replaced, LayerRandomnessSource random) {
for (BiomeVariant variant : variants) {
if (random.nextInt(Integer.MAX_VALUE) < variant.getChance() * Integer.MAX_VALUE) {
return variant.getVariant();
}
}
return replaced;
}
}
}

View file

@ -0,0 +1,79 @@
/*
* 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.biomes;
import com.google.common.base.Preconditions;
import net.minecraft.world.biome.Biome;
import net.minecraft.world.biome.layer.LayerRandomnessSource;
import java.util.ArrayList;
import java.util.List;
/**
* Picks biomes with arbitrary double weights using a binary search.
*/
public final class WeightedBiomePicker {
private double currentTotal;
private List<ContinentalBiomeEntry> entries;
WeightedBiomePicker() {
currentTotal = 0;
entries = new ArrayList<>();
}
void addBiome(final Biome biome, final double weight) {
currentTotal += weight;
entries.add(new ContinentalBiomeEntry(biome, weight, currentTotal));
}
double getCurrentWeightTotal() {
return currentTotal;
}
public Biome pickRandom(LayerRandomnessSource random) {
double target = (double) random.nextInt(Integer.MAX_VALUE) * getCurrentWeightTotal() / Integer.MAX_VALUE;
return search(target).getBiome();
}
/**
* Searches with the specified target value
*
* @param target The target value, must satisfy the constraint 0 <= target <= currentTotal
* @return The result of the search
*/
ContinentalBiomeEntry search(final double target) {
// Sanity checks, fail fast if stuff is going wrong.
Preconditions.checkArgument(target <= currentTotal, "The provided target value for biome selection must be less than or equal to the weight total");
Preconditions.checkArgument(target >= 0, "The provided target value for biome selection cannot be negative");
int low = 0;
int high = entries.size() - 1;
while (low < high) {
int mid = (high + low) >>> 1;
if (target < entries.get(mid).getUpperWeightBound()) {
high = mid;
} else {
low = mid + 1;
}
}
return entries.get(low);
}
}

View file

@ -0,0 +1,47 @@
/*
* 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.biomes;
import net.fabricmc.fabric.api.biomes.v1.OverworldBiomes;
import net.fabricmc.fabric.impl.biomes.InternalBiomeData;
import net.fabricmc.fabric.impl.biomes.InternalBiomeUtils;
import net.minecraft.util.registry.Registry;
import net.minecraft.world.biome.Biome;
import net.minecraft.world.biome.layer.AddEdgeBiomesLayer;
import net.minecraft.world.biome.layer.LayerRandomnessSource;
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;
/**
* Adds edges and shores specified in {@link OverworldBiomes#addEdgeBiome(Biome, Biome, double)} and {@link OverworldBiomes#addShoreBiome(Biome, Biome, 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 = Registry.BIOME.get(center);
if (InternalBiomeData.getOverworldShores().containsKey(centerBiome) && InternalBiomeUtils.neighborsOcean(north, east, south, west)) {
info.setReturnValue(Registry.BIOME.getRawId(InternalBiomeData.getOverworldShores().get(centerBiome).pickRandom(rand)));
} else if (InternalBiomeData.getOverworldEdges().containsKey(centerBiome) && InternalBiomeUtils.isEdge(north, east, south, west, center)) {
info.setReturnValue(Registry.BIOME.getRawId(InternalBiomeData.getOverworldEdges().get(centerBiome).pickRandom(rand)));
}
}
}

View file

@ -0,0 +1,93 @@
/*
* 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.biomes;
import net.fabricmc.fabric.api.biomes.v1.OverworldBiomes;
import net.fabricmc.fabric.impl.biomes.InternalBiomeData;
import net.fabricmc.fabric.impl.biomes.WeightedBiomePicker;
import net.minecraft.util.registry.Registry;
import net.minecraft.world.biome.Biome;
import net.minecraft.world.biome.layer.AddHillsLayer;
import net.minecraft.world.biome.layer.BiomeLayers;
import net.minecraft.world.biome.layer.LayerRandomnessSource;
import net.minecraft.world.biome.layer.LayerSampler;
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;
/**
* Injects hills biomes specified from {@link OverworldBiomes#addHillsBiome(Biome, Biome, double)}into the default hills layer
*/
@Mixin(AddHillsLayer.class)
public class MixinAddHillsLayer {
@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()) {
// No use doing anything if there are no hills registered. Fall through to vanilla logic.
return;
}
final int biomeId = biomeSampler.sample(chunkX, chunkZ);
int noiseSample = noiseSampler.sample(chunkX, chunkZ);
int processedNoiseSample = (noiseSample - 2) % 29;
final Biome biome = Registry.BIOME.get(biomeId);
WeightedBiomePicker hillPicker = InternalBiomeData.getOverworldHills().get(biome);
if (hillPicker == null) {
// No hills for this biome, fall through to vanilla logic.
return;
}
if (rand.nextInt(3) == 0 || processedNoiseSample == 0) {
int biomeReturn = Registry.BIOME.getRawId(hillPicker.pickRandom(rand));
Biome parent;
if (processedNoiseSample == 0 && biomeReturn != biomeId) {
parent = Biome.getParentBiome(Registry.BIOME.get(biomeReturn));
biomeReturn = parent == null ? biomeId : Registry.BIOME.getRawId(parent);
}
if (biomeReturn != biomeId) {
int similarity = 0;
if (BiomeLayers.areSimilar(biomeSampler.sample(chunkX, chunkZ - 1), biomeId)) {
++similarity;
}
if (BiomeLayers.areSimilar(biomeSampler.sample(chunkX + 1, chunkZ), biomeId)) {
++similarity;
}
if (BiomeLayers.areSimilar(biomeSampler.sample(chunkX - 1, chunkZ), biomeId)) {
++similarity;
}
if (BiomeLayers.areSimilar(biomeSampler.sample(chunkX, chunkZ + 1), biomeId)) {
++similarity;
}
if (similarity >= 3) {
info.setReturnValue(biomeReturn);
return;
}
}
}
// Cancel vanilla logic.
info.setReturnValue(biomeId);
}
}

View file

@ -0,0 +1,58 @@
/*
* 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.biomes;
import net.fabricmc.fabric.api.biomes.v1.OverworldBiomes;
import net.fabricmc.fabric.impl.biomes.InternalBiomeData;
import net.minecraft.util.registry.Registry;
import net.minecraft.world.biome.Biome;
import net.minecraft.world.biome.layer.AddRiversLayer;
import net.minecraft.world.biome.layer.LayerRandomnessSource;
import net.minecraft.world.biome.layer.LayerSampler;
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 java.util.Map;
/**
* Sets river biomes specified with {@link OverworldBiomes#setRiverBiome(Biome, Biome)}
*/
@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 = Registry.BIOME.get(landBiomeId);
int riverBiomeId = riverSampler.sample(x, z);
Map<Biome, Biome> overworldRivers = InternalBiomeData.getOverworldRivers();
if (overworldRivers.containsKey(landBiome) && riverBiomeId == RIVER_ID) {
Biome riverBiome = overworldRivers.get(landBiome);
info.setReturnValue(riverBiome == null ? landBiomeId : Registry.BIOME.getRawId(riverBiome));
}
}
}

View file

@ -0,0 +1,52 @@
/*
* 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.biomes;
import net.fabricmc.fabric.impl.biomes.InternalBiomeData;
import net.minecraft.world.biome.Biome;
import net.minecraft.world.biome.source.BiomeSource;
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 java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
/**
* 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

@ -0,0 +1,116 @@
/*
* 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.biomes;
import net.fabricmc.fabric.api.biomes.v1.OverworldClimate;
import net.fabricmc.fabric.impl.biomes.InternalBiomeUtils;
import net.minecraft.util.registry.Registry;
import net.minecraft.world.biome.Biome;
import net.minecraft.world.biome.layer.LayerRandomnessSource;
import net.minecraft.world.biome.layer.SetBaseBiomesLayer;
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.CallbackInfoReturnable;
/**
* Injects biomes into the arrays of biomes in the {@link SetBaseBiomesLayer}
*/
@Mixin(SetBaseBiomesLayer.class)
public class MixinSetBaseBiomesLayer {
@Shadow
@Final
@Mutable
private static int[] SNOWY_BIOMES;
@Shadow
@Final
@Mutable
private static int[] COOL_BIOMES;
@Shadow
@Final
@Mutable
private static int[] TEMPERATE_BIOMES;
@Shadow
@Final
@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);
}
@Inject(at = @At(value = "FIELD", target = "Lnet/minecraft/world/biome/layer/SetBaseBiomesLayer;TEMPERATE_BIOMES:[I"), method = "sample", cancellable = true)
private void injectTemperateBiomes(LayerRandomnessSource random, int value, CallbackInfoReturnable<Integer> info) {
InternalBiomeUtils.injectBiomesIntoClimate(random, TEMPERATE_BIOMES, OverworldClimate.TEMPERATE, info::setReturnValue);
}
@Inject(at = @At(value = "FIELD", target = "Lnet/minecraft/world/biome/layer/SetBaseBiomesLayer;SNOWY_BIOMES:[I"), method = "sample", cancellable = true)
private void injectSnowyBiomes(LayerRandomnessSource random, int value, CallbackInfoReturnable<Integer> info) {
InternalBiomeUtils.injectBiomesIntoClimate(random, SNOWY_BIOMES, OverworldClimate.SNOWY, info::setReturnValue);
}
@Inject(at = @At(value = "FIELD", target = "Lnet/minecraft/world/biome/layer/SetBaseBiomesLayer;COOL_BIOMES:[I"), method = "sample", cancellable = true)
private void injectCoolBiomes(LayerRandomnessSource random, int value, CallbackInfoReturnable<Integer> info) {
InternalBiomeUtils.injectBiomesIntoClimate(random, COOL_BIOMES, OverworldClimate.COOL, info::setReturnValue);
}
@Inject(at = @At("RETURN"), method = "sample", cancellable = true)
private void transformVariants(LayerRandomnessSource random, int value, CallbackInfoReturnable<Integer> info) {
int biomeId = info.getReturnValueI();
Biome biome = Registry.BIOME.get(biomeId);
// Determine what special case this is...
OverworldClimate climate;
if (biomeId == BADLANDS_PLATEAU_ID || biomeId == WOODED_BADLANDS_PLATEAU_ID) {
climate = OverworldClimate.DRY;
} else if (biomeId == JUNGLE_ID) {
climate = OverworldClimate.TEMPERATE;
} else if (biomeId == GIANT_TREE_TAIGA_ID) {
climate = OverworldClimate.TEMPERATE;
} else {
climate = null;
}
info.setReturnValue(InternalBiomeUtils.transformBiome(random, biome, climate));
}
}

View file

@ -0,0 +1,77 @@
/*
* 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.biomes;
import net.fabricmc.fabric.impl.biomes.InternalBiomeData;
import net.minecraft.block.BlockState;
import net.minecraft.world.biome.Biome;
import net.minecraft.world.biome.source.VanillaLayeredBiomeSource;
import net.minecraft.world.gen.feature.StructureFeature;
import org.spongepowered.asm.mixin.*;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import java.util.List;
import java.util.Set;
/**
* 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 Biome[] biomes;
@Unique
private int injectionCount;
@Inject(at = @At("HEAD"), method = "hasStructureFeature")
private void hasStructureFeature(CallbackInfoReturnable<Boolean> info) {
updateInjections();
}
@Inject(at = @At("HEAD"), method = "getTopMaterials")
private void getTopMaterials(CallbackInfoReturnable<Set<BlockState>> info) {
updateInjections();
}
@Unique
private void updateInjections() {
List<Biome> injectedBiomes = InternalBiomeData.getOverworldInjectedBiomes();
int currentSize = injectedBiomes.size();
if (this.injectionCount < currentSize) {
List<Biome> toInject = injectedBiomes.subList(injectionCount, currentSize - 1);
Biome[] oldBiomes = this.biomes;
this.biomes = new Biome[oldBiomes.length + toInject.size()];
System.arraycopy(oldBiomes, 0, this.biomes, 0, oldBiomes.length);
int index = oldBiomes.length;
for (Biome injected : toInject) {
biomes[index++] = injected;
}
injectionCount += toInject.size();
}
}
}

View file

@ -0,0 +1,16 @@
{
"required": true,
"package": "net.fabricmc.fabric.mixin.biomes",
"compatibilityLevel": "JAVA_8",
"mixins": [
"MixinAddEdgeBiomesLayer",
"MixinAddHillsLayer",
"MixinAddRiversLayer",
"MixinBiomeSource",
"MixinSetBaseBiomesLayer",
"MixinVanillaLayeredBiomeSource"
],
"injectors": {
"defaultRequire": 1
}
}

View file

@ -0,0 +1,18 @@
{
"schemaVersion": 1,
"id": "fabric-biomes-v1",
"version": "${version}",
"name": "fabric-biomes-v1",
"license": "Apache-2.0",
"environment": "*",
"depends": {
"fabricloader": ">=0.4.0"
},
"mixins": [
"fabric-biomes-v1.mixins.json"
]
}

View file

@ -20,18 +20,13 @@ import net.fabricmc.fabric.impl.registry.LootEntryTypeRegistryImpl;
import net.minecraft.world.loot.entry.LootEntry;
/**
* Fabric's extensions to {@code net.minecraft.world.loot.entry.LootEntries} for registering
* custom loot entry types.
*
* @see #register
* @deprecated Use {@link net.fabricmc.fabric.api.loot.v1.LootEntryTypeRegistry}
*/
@Deprecated
public interface LootEntryTypeRegistry {
@Deprecated
final LootEntryTypeRegistry INSTANCE = LootEntryTypeRegistryImpl.INSTANCE;
/**
* Registers a loot entry type by its serializer.
*
* @param serializer the loot entry serializer
*/
@Deprecated
void register(LootEntry.Serializer<?> serializer);
}

View file

@ -21,10 +21,9 @@ import net.minecraft.world.loot.entry.LootEntries;
import net.minecraft.world.loot.entry.LootEntry;
import java.lang.reflect.Method;
import java.util.function.Consumer;
@Deprecated
public final class LootEntryTypeRegistryImpl implements LootEntryTypeRegistry {
private static Consumer<LootEntry.Serializer<?>> registerFunction;
public static final LootEntryTypeRegistryImpl INSTANCE = new LootEntryTypeRegistryImpl();
private static final Method REGISTER_METHOD;

View file

@ -105,12 +105,12 @@ public final class FabricItemGroupBuilder {
}
@Override
public void appendItems(DefaultedList<ItemStack> stacks) {
public void appendStacks(DefaultedList<ItemStack> stacks) {
if (stacksForDisplay != null) {
stacksForDisplay.accept(stacks);
return;
}
super.appendItems(stacks);
super.appendStacks(stacks);
}
};
}

View file

@ -23,7 +23,7 @@ import net.minecraft.client.gui.screen.ingame.CreativeInventoryScreen;
import net.minecraft.container.Container;
import net.minecraft.entity.player.PlayerInventory;
import net.minecraft.item.ItemGroup;
import net.minecraft.network.chat.Component;
import net.minecraft.text.Text;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
@ -34,7 +34,7 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
@Mixin(CreativeInventoryScreen.class)
public abstract class MixinCreativePlayerInventoryGui extends AbstractInventoryScreen implements CreativeGuiExtensions {
public MixinCreativePlayerInventoryGui(Container container_1, PlayerInventory playerInventory_1, Component textComponent_1) {
public MixinCreativePlayerInventoryGui(Container container_1, PlayerInventory playerInventory_1, Text textComponent_1) {
super(container_1, playerInventory_1, textComponent_1);
}

View file

@ -0,0 +1,3 @@
{
"fabric.gui.creativeTabPage": "Leht %d/%d"
}

View file

@ -1,5 +1,5 @@
archivesBaseName = "fabric-keybindings-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,7 @@
package net.fabricmc.fabric.api.client.keybinding;
import net.fabricmc.fabric.mixin.client.keybinding.KeyCodeAccessor;
import net.minecraft.client.options.KeyBinding;
import net.minecraft.client.util.InputUtil;
import net.minecraft.util.Identifier;
@ -31,6 +32,14 @@ public class FabricKeyBinding extends KeyBinding {
super("key." + id.toString().replace(':', '.'), type, code, category);
}
/**
* Returns the configured KeyCode assigned to the KeyBinding from the player's settings.
* @return configured KeyCode
*/
public InputUtil.KeyCode getBoundKey() {
return ((KeyCodeAccessor) this).getKeyCode();
}
public static class Builder {
protected final FabricKeyBinding binding;

View file

@ -14,11 +14,15 @@
* limitations under the License.
*/
package net.fabricmc.fabric.impl.registry.callbacks;
package net.fabricmc.fabric.mixin.client.keybinding;
import net.minecraft.util.Identifier;
import net.minecraft.client.options.KeyBinding;
import net.minecraft.client.util.InputUtil;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;
@FunctionalInterface
public interface RegistryPreRegisterCallback<T> extends RegistryCallback<T> {
void onPreRegister(int rawId, Identifier id, T object, boolean isNewToRegistry);
@Mixin(KeyBinding.class)
public interface KeyCodeAccessor {
@Accessor
InputUtil.KeyCode getKeyCode();
}

View file

@ -4,7 +4,8 @@
"compatibilityLevel": "JAVA_8",
"client": [
"MixinGameOptions",
"MixinKeyBinding"
"MixinKeyBinding",
"KeyCodeAccessor"
],
"injectors": {
"defaultRequire": 1

View file

@ -0,0 +1,6 @@
archivesBaseName = "fabric-loot-tables-v1"
version = getSubprojectVersion(project, "0.1.0")
dependencies {
compile project(path: ':fabric-api-base', configuration: 'dev')
}

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.api.loot.v1;
import net.minecraft.world.loot.LootPool;
import net.minecraft.world.loot.LootTableRange;
import net.minecraft.world.loot.condition.LootCondition;
import net.minecraft.world.loot.entry.LootEntry;
import net.minecraft.world.loot.function.LootFunction;
import java.util.List;
/**
* An interface implemented by all {@code net.minecraft.world.loot.LootPool} instances when
* Fabric API is present. Contains accessors for various fields.
*/
public interface FabricLootPool {
default LootPool asVanilla() {
return (LootPool) this;
}
List<LootEntry> getEntries();
List<LootCondition> getConditions();
List<LootFunction> getFunctions();
LootTableRange getRollsRange();
}

View file

@ -0,0 +1,110 @@
/*
* 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.loot.v1;
import net.fabricmc.fabric.mixin.loot.LootPoolBuilderHooks;
import net.minecraft.world.loot.LootPool;
import net.minecraft.world.loot.LootTableRange;
import net.minecraft.world.loot.condition.LootCondition;
import net.minecraft.world.loot.entry.LootEntry;
import net.minecraft.world.loot.function.LootFunction;
public class FabricLootPoolBuilder extends LootPool.Builder {
private final LootPoolBuilderHooks extended = (LootPoolBuilderHooks) this;
private FabricLootPoolBuilder() {}
private FabricLootPoolBuilder(LootPool pool) {
copyFrom(pool, true);
}
@Override
public FabricLootPoolBuilder withRolls(LootTableRange range) {
super.withRolls(range);
return this;
}
@Override
public FabricLootPoolBuilder withEntry(LootEntry.Builder<?> entry) {
super.withEntry(entry);
return this;
}
@Override
public FabricLootPoolBuilder withCondition(LootCondition.Builder condition) {
super.method_356(condition);
return this;
}
@Override
public FabricLootPoolBuilder withFunction(LootFunction.Builder function) {
super.method_353(function);
return this;
}
public FabricLootPoolBuilder withEntry(LootEntry entry) {
extended.getEntries().add(entry);
return this;
}
public FabricLootPoolBuilder withCondition(LootCondition condition) {
extended.getConditions().add(condition);
return this;
}
public FabricLootPoolBuilder withFunction(LootFunction function) {
extended.getFunctions().add(function);
return this;
}
/**
* Copies the entries, conditions and functions of the {@code pool} to this
* builder.
*
* This is equal to {@code copyFrom(pool, false)}.
*/
public FabricLootPoolBuilder copyFrom(LootPool pool) {
return copyFrom(pool, false);
}
/**
* Copies the entries, conditions and functions of the {@code pool} to this
* builder.
*
* If {@code copyRolls} is true, the {@link FabricLootPool#getRollsRange rolls} of the pool are also copied.
*/
public FabricLootPoolBuilder copyFrom(LootPool pool, boolean copyRolls) {
FabricLootPool extendedPool = (FabricLootPool) pool;
extended.getConditions().addAll(extendedPool.getConditions());
extended.getFunctions().addAll(extendedPool.getFunctions());
extended.getEntries().addAll(extendedPool.getEntries());
if (copyRolls) {
withRolls(extendedPool.getRollsRange());
}
return this;
}
public static FabricLootPoolBuilder builder() {
return new FabricLootPoolBuilder();
}
public static FabricLootPoolBuilder of(LootPool pool) {
return new FabricLootPoolBuilder(pool);
}
}

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.api.loot.v1;
import net.minecraft.world.loot.LootPool;
import net.minecraft.world.loot.LootSupplier;
import net.minecraft.world.loot.context.LootContextType;
import net.minecraft.world.loot.function.LootFunction;
import java.util.List;
/**
* An interface implemented by all {@code net.minecraft.world.loot.LootSupplier} instances when
* Fabric API is present. Contains accessors for various fields.
*/
public interface FabricLootSupplier {
default LootSupplier asVanilla() {
return (LootSupplier) this;
}
List<LootPool> getPools();
List<LootFunction> getFunctions();
LootContextType getType();
}

View file

@ -0,0 +1,105 @@
/*
* 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.loot.v1;
import net.fabricmc.fabric.mixin.loot.LootSupplierBuilderHooks;
import net.minecraft.world.loot.LootPool;
import net.minecraft.world.loot.LootSupplier;
import net.minecraft.world.loot.context.LootContextType;
import net.minecraft.world.loot.function.LootFunction;
import java.util.Collection;
public class FabricLootSupplierBuilder extends LootSupplier.Builder {
private final LootSupplierBuilderHooks extended = (LootSupplierBuilderHooks) this;
protected FabricLootSupplierBuilder() {}
private FabricLootSupplierBuilder(LootSupplier supplier) {
copyFrom(supplier, true);
}
@Override
public FabricLootSupplierBuilder withPool(LootPool.Builder pool) {
super.withPool(pool);
return this;
}
@Override
public FabricLootSupplierBuilder withType(LootContextType type) {
super.withType(type);
return this;
}
@Override
public FabricLootSupplierBuilder withFunction(LootFunction.Builder function) {
super.method_335(function);
return this;
}
public FabricLootSupplierBuilder withPool(LootPool pool) {
extended.getPools().add(pool);
return this;
}
public FabricLootSupplierBuilder withFunction(LootFunction function) {
extended.getFunctions().add(function);
return this;
}
public FabricLootSupplierBuilder withPools(Collection<LootPool> pools) {
pools.forEach(this::withPool);
return this;
}
public FabricLootSupplierBuilder withFunctions(Collection<LootFunction> functions) {
functions.forEach(this::withFunction);
return this;
}
/**
* Copies the pools and functions of the {@code supplier} to this builder.
* This is equal to {@code copyFrom(supplier, false)}.
*/
public FabricLootSupplierBuilder copyFrom(LootSupplier supplier) {
return copyFrom(supplier, false);
}
/**
* Copies the pools and functions of the {@code supplier} to this builder.
* If {@code copyType} is true, the {@link FabricLootSupplier#getType type} of the supplier is also copied.
*/
public FabricLootSupplierBuilder copyFrom(LootSupplier supplier, boolean copyType) {
FabricLootSupplier extendedSupplier = (FabricLootSupplier) supplier;
extended.getPools().addAll(extendedSupplier.getPools());
extended.getFunctions().addAll(extendedSupplier.getFunctions());
if (copyType) {
withType(extendedSupplier.getType());
}
return this;
}
public static FabricLootSupplierBuilder builder() {
return new FabricLootSupplierBuilder();
}
public static FabricLootSupplierBuilder of(LootSupplier supplier) {
return new FabricLootSupplierBuilder(supplier);
}
}

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.api.loot.v1;
import net.fabricmc.fabric.impl.loot.LootEntryTypeRegistryImpl;
import net.minecraft.world.loot.entry.LootEntry;
/**
* Fabric's extensions to {@code net.minecraft.world.loot.entry.LootEntries} for registering
* custom loot entry types.
*
* @see #register
*/
public interface LootEntryTypeRegistry {
final LootEntryTypeRegistry INSTANCE = LootEntryTypeRegistryImpl.INSTANCE;
/**
* Registers a loot entry type by its serializer.
*
* @param serializer the loot entry serializer
*/
void register(LootEntry.Serializer<?> serializer);
}

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.api.loot.v1;
import com.google.gson.Gson;
import net.minecraft.resource.Resource;
import net.minecraft.resource.ResourceManager;
import net.minecraft.util.Identifier;
import net.minecraft.util.JsonHelper;
import net.minecraft.util.Lazy;
import net.minecraft.world.loot.*;
import net.minecraft.world.loot.condition.LootCondition;
import net.minecraft.world.loot.entry.LootEntry;
import net.minecraft.world.loot.function.LootFunction;
import org.apache.commons.io.IOUtils;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.lang.reflect.Field;
import java.nio.charset.StandardCharsets;
import java.util.Optional;
import java.util.stream.Stream;
public final class LootJsonParser {
/* Reading this from LootManager to access all serializers from vanilla. */
private static final Lazy<Gson> GSON = new Lazy<>(() -> {
try {
Field gsonField = Stream.of(LootManager.class.getDeclaredFields())
.filter(field -> field.getType() == Gson.class)
.findFirst()
.orElseThrow(() -> new RuntimeException("Gson not found in LootManager!"));
gsonField.setAccessible(true);
return (Gson) gsonField.get(null);
} catch (Exception e) {
throw new RuntimeException("Exception while getting Gson instance from LootManager", e);
}
});
private LootJsonParser() {
}
public static <T> T read(Reader json, Class<T> c) {
return JsonHelper.deserialize(GSON.get(), json, c);
}
public static <T> T read(String json, Class<T> c) {
return JsonHelper.deserialize(GSON.get(), json, c);
}
}

View file

@ -0,0 +1,48 @@
/*
* 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.loot.v1.event;
import net.fabricmc.fabric.api.event.Event;
import net.fabricmc.fabric.api.event.EventFactory;
import net.fabricmc.fabric.api.loot.v1.FabricLootSupplierBuilder;
import net.minecraft.resource.ResourceManager;
import net.minecraft.util.Identifier;
import net.minecraft.world.loot.LootManager;
import net.minecraft.world.loot.LootSupplier;
/**
* An event handler that is called when loot tables are loaded.
* Use {@link #EVENT} to register instances.
*/
@FunctionalInterface
public interface LootTableLoadingCallback {
@FunctionalInterface
interface LootTableSetter {
void set(LootSupplier supplier);
}
final Event<LootTableLoadingCallback> EVENT = EventFactory.createArrayBacked(
LootTableLoadingCallback.class,
(listeners) -> (resourceManager, manager, id, supplier, setter) -> {
for (LootTableLoadingCallback callback : listeners) {
callback.onLootTableLoading(resourceManager, manager, id, supplier, setter);
}
}
);
void onLootTableLoading(ResourceManager resourceManager, LootManager manager, Identifier id, FabricLootSupplierBuilder supplier, LootTableSetter setter);
}

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.impl.loot;
import net.minecraft.world.loot.entry.LootEntries;
import net.minecraft.world.loot.entry.LootEntry;
import java.lang.reflect.Method;
public final class LootEntryTypeRegistryImpl implements net.fabricmc.fabric.api.loot.v1.LootEntryTypeRegistry {
public static final LootEntryTypeRegistryImpl INSTANCE = new LootEntryTypeRegistryImpl();
private static final Method REGISTER_METHOD;
static {
Method target = null;
for (Method m : LootEntries.class.getDeclaredMethods()) {
if (m.getParameterCount() == 1 && m.getParameterTypes()[0] == LootEntry.Serializer.class) {
if (target != null) {
throw new RuntimeException("More than one register-like method found in LootEntries!");
} else {
target = m;
}
}
}
if (target == null) {
throw new RuntimeException("Could not find register-like method in LootEntries!");
} else {
REGISTER_METHOD = target;
REGISTER_METHOD.setAccessible(true);
}
}
private LootEntryTypeRegistryImpl() {
}
@Override
public void register(LootEntry.Serializer<?> serializer) {
try {
REGISTER_METHOD.invoke(null, serializer);
} catch (Throwable t) {
throw new RuntimeException(t);
}
}
}

View file

@ -0,0 +1,36 @@
/*
* 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.loot;
import net.minecraft.world.loot.LootPool;
import net.minecraft.world.loot.condition.LootCondition;
import net.minecraft.world.loot.entry.LootEntry;
import net.minecraft.world.loot.function.LootFunction;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;
import java.util.List;
@Mixin(LootPool.Builder.class)
public interface LootPoolBuilderHooks {
@Accessor
List<LootEntry> getEntries();
@Accessor
List<LootCondition> getConditions();
@Accessor
List<LootFunction> getFunctions();
}

View file

@ -0,0 +1,33 @@
/*
* 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.loot;
import net.minecraft.world.loot.LootPool;
import net.minecraft.world.loot.LootSupplier;
import net.minecraft.world.loot.function.LootFunction;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;
import java.util.List;
@Mixin(LootSupplier.Builder.class)
public interface LootSupplierBuilderHooks {
@Accessor
List<LootPool> getPools();
@Accessor
List<LootFunction> getFunctions();
}

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.mixin.loot;
import com.google.common.collect.ImmutableMap;
import com.google.gson.JsonObject;
import net.fabricmc.fabric.api.loot.v1.event.LootTableLoadingCallback;
import net.fabricmc.fabric.api.loot.v1.FabricLootSupplierBuilder;
import net.minecraft.resource.ResourceManager;
import net.minecraft.util.Identifier;
import net.minecraft.util.profiler.Profiler;
import net.minecraft.world.loot.LootManager;
import net.minecraft.world.loot.LootSupplier;
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.CallbackInfo;
import java.util.HashMap;
import java.util.Map;
@Mixin(LootManager.class)
public class MixinLootManager {
@Shadow private Map<Identifier, LootSupplier> suppliers;
@Inject(method = "method_20712", at = @At("RETURN"))
private void apply(Map<Identifier, JsonObject> objectMap, ResourceManager manager, Profiler profiler, CallbackInfo info) {
Map<Identifier, LootSupplier> newSuppliers = new HashMap<>();
suppliers.forEach((id, supplier) -> {
FabricLootSupplierBuilder builder = FabricLootSupplierBuilder.of(supplier);
//noinspection ConstantConditions
LootTableLoadingCallback.EVENT.invoker().onLootTableLoading(
manager, (LootManager) (Object) this, id, builder, (s) -> newSuppliers.put(id, s)
);
newSuppliers.computeIfAbsent(id, (i) -> builder.create());
});
suppliers = ImmutableMap.copyOf(newSuppliers);
}
}

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.mixin.loot;
import net.fabricmc.fabric.api.loot.v1.FabricLootPool;
import net.minecraft.world.loot.LootPool;
import net.minecraft.world.loot.LootTableRange;
import net.minecraft.world.loot.condition.LootCondition;
import net.minecraft.world.loot.entry.LootEntry;
import net.minecraft.world.loot.function.LootFunction;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.gen.Accessor;
import java.util.Arrays;
import java.util.List;
@Mixin(LootPool.class)
public abstract class MixinLootPool implements FabricLootPool {
@Shadow @Final private LootEntry[] entries;
@Shadow @Final private LootCondition[] conditions;
@Shadow @Final private LootFunction[] functions;
@Override
public List<LootEntry> getEntries() {
return Arrays.asList(entries);
}
@Override
public List<LootCondition> getConditions() {
return Arrays.asList(conditions);
}
@Override
public List<LootFunction> getFunctions() {
return Arrays.asList(functions);
}
@Accessor
@Override
public abstract LootTableRange getRollsRange();
}

View file

@ -0,0 +1,50 @@
/*
* 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.loot;
import net.fabricmc.fabric.api.loot.v1.FabricLootSupplier;
import net.minecraft.world.loot.LootPool;
import net.minecraft.world.loot.LootSupplier;
import net.minecraft.world.loot.context.LootContextType;
import net.minecraft.world.loot.function.LootFunction;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.gen.Accessor;
import java.util.Arrays;
import java.util.List;
@Mixin(LootSupplier.class)
public abstract class MixinLootSupplier implements FabricLootSupplier {
@Shadow @Final private LootPool[] pools;
@Shadow @Final private LootFunction[] functions;
@Override
public List<LootPool> getPools() {
return Arrays.asList(pools);
}
@Override
public List<LootFunction> getFunctions() {
return Arrays.asList(functions);
}
@Accessor
@Override
public abstract LootContextType getType();
}

View file

@ -0,0 +1,15 @@
{
"required": true,
"package": "net.fabricmc.fabric.mixin.loot",
"compatibilityLevel": "JAVA_8",
"mixins": [
"LootPoolBuilderHooks",
"LootSupplierBuilderHooks",
"MixinLootManager",
"MixinLootPool",
"MixinLootSupplier"
],
"injectors": {
"defaultRequire": 1
}
}

View file

@ -0,0 +1,13 @@
{
"schemaVersion": 1,
"id": "fabric-loot-tables-v1",
"version": "${version}",
"license": "Apache-2.0",
"depends": {
"fabricloader": ">=0.4.0",
"fabric-api-base": "*"
},
"mixins": [
"fabric-loot-tables-v1.mixins.json"
]
}

View file

@ -88,7 +88,7 @@ public final class ToolManager {
private static int getMiningLevel(ItemStack stack) {
if (stack.getItem() instanceof ToolItem) {
return ((ToolItem) stack.getItem()).getType().getMiningLevel();
return ((ToolItem) stack.getItem()).getMaterial().getMiningLevel();
} else {
return 0;
}
@ -102,7 +102,7 @@ public final class ToolManager {
if (entry != null) {
Item item = stack.getItem();
for (int i = 0; i < entry.tags.length; i++) {
if (item.matches(entry.tags[i])) {
if (item.isIn(entry.tags[i])) {
return TriState.of(getMiningLevel(stack) >= entry.tagLevels[i]);
}
}

View file

@ -23,5 +23,5 @@ import org.spongepowered.asm.mixin.gen.Accessor;
@Mixin(MiningToolItem.class)
public interface MiningToolItemAccessor {
@Accessor
float getBlockBreakingSpeed();
float getMiningSpeed();
}

View file

@ -41,12 +41,12 @@ public abstract class MixinItemStack {
}
}
@Inject(at = @At("HEAD"), method = "getBlockBreakingSpeed", cancellable = true)
@Inject(at = @At("HEAD"), method = "getMiningSpeed", cancellable = true)
public void getBlockBreakingSpeed(BlockState state, CallbackInfoReturnable<Float> info) {
if (this.getItem() instanceof MiningToolItemAccessor) {
TriState triState = ToolManager.handleIsEffectiveOn((ItemStack) (Object) this, state);
if (triState != TriState.DEFAULT) {
info.setReturnValue(triState.get() ? ((MiningToolItemAccessor) this.getItem()).getBlockBreakingSpeed() : 1.0F);
info.setReturnValue(triState.get() ? ((MiningToolItemAccessor) this.getItem()).getMiningSpeed() : 1.0F);
info.cancel();
}
}

View file

@ -19,7 +19,7 @@ package net.fabricmc.fabric.api.entity;
import net.fabricmc.fabric.impl.entity.FabricEntityType;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityCategory;
import net.minecraft.entity.EntitySize;
import net.minecraft.entity.EntityDimensions;
import net.minecraft.entity.EntityType;
import net.minecraft.world.World;
import org.apache.logging.log4j.LogManager;
@ -44,7 +44,7 @@ public class FabricEntityTypeBuilder<T extends Entity> {
private int updateIntervalTicks = -1;
private Boolean alwaysUpdateVelocity;
private boolean immuneToFire = false;
private EntitySize size = EntitySize.resizeable(-1.0f, -1.0f);
private EntityDimensions size = EntityDimensions.changing(-1.0f, -1.0f);
protected FabricEntityTypeBuilder(EntityCategory category, EntityType.EntityFactory<T> function) {
this.category = category;
@ -83,15 +83,15 @@ public class FabricEntityTypeBuilder<T extends Entity> {
}
/**
* @deprecated Use {@link FabricEntityTypeBuilder#size(EntitySize)}
* @deprecated Use {@link FabricEntityTypeBuilder#size(EntityDimensions)}
*/
@Deprecated
public FabricEntityTypeBuilder<T> size(float width, float height) {
this.size = EntitySize.resizeable(width, height);
this.size = EntityDimensions.changing(width, height);
return this;
}
public FabricEntityTypeBuilder<T> size(EntitySize size) {
public FabricEntityTypeBuilder<T> size(EntityDimensions size) {
this.size = size;
return this;
}

View file

@ -19,14 +19,14 @@ package net.fabricmc.fabric.impl.entity;
import com.mojang.datafixers.types.Type;
import net.minecraft.entity.Entity;
import net.minecraft.entity.EntityCategory;
import net.minecraft.entity.EntitySize;
import net.minecraft.entity.EntityDimensions;
import net.minecraft.entity.EntityType;
public class FabricEntityType<T extends Entity> extends EntityType<T> {
private final int maxTrackDistance, trackTickInterval;
private final Boolean alwaysUpdateVelocity;
public FabricEntityType(EntityFactory<T> entityType$EntityFactory_1, EntityCategory entityCategory_1, boolean boolean_1, boolean boolean_2, boolean boolean_3, Type<?> type_1, EntitySize entitySize_1, int maxTrackDistance, int trackTickInterval, Boolean alwaysUpdateVelocity) {
public FabricEntityType(EntityFactory<T> entityType$EntityFactory_1, EntityCategory entityCategory_1, boolean boolean_1, boolean boolean_2, boolean boolean_3, Type<?> type_1, EntityDimensions entitySize_1, int maxTrackDistance, int trackTickInterval, Boolean alwaysUpdateVelocity) {
super(entityType$EntityFactory_1, entityCategory_1, boolean_1, boolean_2, boolean_3, type_1, entitySize_1);
this.maxTrackDistance = maxTrackDistance;
this.trackTickInterval = trackTickInterval;

View file

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

View file

@ -0,0 +1,36 @@
/*
* 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.event.registry;
import net.fabricmc.fabric.api.event.Event;
import net.fabricmc.fabric.impl.registry.ListenableRegistry;
import net.minecraft.util.Identifier;
import net.minecraft.util.registry.Registry;
@FunctionalInterface
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();
}
}

View file

@ -0,0 +1,36 @@
/*
* 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.event.registry;
import net.fabricmc.fabric.api.event.Event;
import net.fabricmc.fabric.impl.registry.ListenableRegistry;
import net.minecraft.util.Identifier;
import net.minecraft.util.registry.Registry;
@FunctionalInterface
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();
}
}

View file

@ -0,0 +1,57 @@
/*
* 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.event.registry;
import it.unimi.dsi.fastutil.ints.Int2IntMap;
import net.fabricmc.fabric.api.event.Event;
import net.fabricmc.fabric.impl.registry.ListenableRegistry;
import net.minecraft.util.Identifier;
import net.minecraft.util.registry.Registry;
/**
* The remapping process functions as follows:
*
* - RegistryEntryRemovedCallbacks are called to remove any objects culled in the process, with the old numeric ID.
* - RegistryIdRemapCallback is emitted to allow remapping the IDs of objects still present.
* - RegistryEntryAddedCallbacks are called to add any objects added in the process, with the new numeric ID.
*
* RegistryIdRemapCallback is called on every remapping operation, if you want to do your own processing in one swoop
* (say, rebuild the ID map from scratch).
*
* Generally speaking, a remap can only cause object *removals*; object *additions* are necessary to reverse remaps.
*
* @param <T> The registry type.
*/
@FunctionalInterface
public interface RegistryIdRemapCallback<T> {
void onRemap(RemapState<T> state);
interface RemapState<T> {
Int2IntMap getRawIdChangeMap();
Identifier getIdFromOld(int oldRawId);
Identifier getIdFromNew(int newRawId);
}
static <T> Event<RegistryIdRemapCallback<T>> event(Registry<T> registry) {
if (!(registry instanceof ListenableRegistry)) {
throw new IllegalArgumentException("Unsupported registry: " + registry.getClass().getName());
}
//noinspection unchecked
return (Event<RegistryIdRemapCallback<T>>) ((ListenableRegistry) registry).fabric_getRemapEvent();
}
}

View file

@ -20,7 +20,7 @@ import net.fabricmc.api.ClientModInitializer;
import net.fabricmc.fabric.api.network.ClientSidePacketRegistry;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.network.ClientPlayerEntity;
import net.minecraft.network.chat.TextComponent;
import net.minecraft.text.LiteralText;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@ -31,11 +31,11 @@ public class FabricRegistryClientInit implements ClientModInitializer {
public void onInitializeClient() {
ClientSidePacketRegistry.INSTANCE.register(RegistrySyncManager.ID, (ctx, buf) -> {
// if not hosting server, apply packet
RegistrySyncManager.receivePacket(ctx, buf, !MinecraftClient.getInstance().isInSingleplayer(), (e) -> {
RegistrySyncManager.receivePacket(ctx, buf, RegistrySyncManager.DEBUG || !MinecraftClient.getInstance().isInSingleplayer(), (e) -> {
LOGGER.error("Registry remapping failed!", e);
MinecraftClient.getInstance().execute(() -> {
((ClientPlayerEntity) ctx.getPlayer()).networkHandler.getClientConnection().disconnect(
new TextComponent("Registry remapping failed: " + e.getMessage())
new LiteralText("Registry remapping failed: " + e.getMessage())
);
});
});

View file

@ -17,12 +17,12 @@
package net.fabricmc.fabric.impl.registry;
import net.fabricmc.fabric.api.event.Event;
import net.fabricmc.fabric.impl.registry.callbacks.RegistryPostRegisterCallback;
import net.fabricmc.fabric.impl.registry.callbacks.RegistryPreClearCallback;
import net.fabricmc.fabric.impl.registry.callbacks.RegistryPreRegisterCallback;
import net.fabricmc.fabric.api.event.registry.RegistryEntryAddedCallback;
import net.fabricmc.fabric.api.event.registry.RegistryIdRemapCallback;
import net.fabricmc.fabric.api.event.registry.RegistryEntryRemovedCallback;
public interface ListenableRegistry<T> {
Event<RegistryPreClearCallback<T>> getPreClearEvent();
Event<RegistryPreRegisterCallback<T>> getPreRegisterEvent();
Event<RegistryPostRegisterCallback<T>> getPostRegisterEvent();
Event<RegistryEntryAddedCallback<T>> fabric_getAddObjectEvent();
Event<RegistryEntryRemovedCallback<T>> fabric_getRemoveObjectEvent();
Event<RegistryIdRemapCallback<T>> fabric_getRemapEvent();
}

View file

@ -16,8 +16,12 @@
package net.fabricmc.fabric.impl.registry;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import io.netty.buffer.Unpooled;
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
import it.unimi.dsi.fastutil.ints.IntSet;
import it.unimi.dsi.fastutil.objects.Object2IntMap;
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
import net.fabricmc.fabric.api.network.PacketContext;
@ -29,7 +33,13 @@ import net.minecraft.util.PacketByteBuf;
import net.minecraft.util.registry.MutableRegistry;
import net.minecraft.util.registry.Registry;
import net.minecraft.util.registry.SimpleRegistry;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
@ -37,7 +47,10 @@ import java.util.concurrent.TimeoutException;
import java.util.function.Consumer;
public final class RegistrySyncManager {
public static final Identifier ID = new Identifier("fabric", "registry/sync");
static final boolean DEBUG = System.getProperty("fabric.registry.debug", "false").equalsIgnoreCase("true");
static final Identifier ID = new Identifier("fabric", "registry/sync");
private static final Logger LOGGER = LogManager.getLogger();
private static final boolean DEBUG_WRITE_REGISTRY_DATA = System.getProperty("fabric.registry.debug.writeContentsAsCsv", "false").equalsIgnoreCase("true");
private static final Set<Identifier> REGISTRY_BLACKLIST = ImmutableSet.of();
private static final Set<Identifier> REGISTRY_BLACKLIST_NETWORK = ImmutableSet.of();
@ -80,6 +93,39 @@ public final class RegistrySyncManager {
CompoundTag mainTag = new CompoundTag();
for (Identifier registryId : Registry.REGISTRIES.getIds()) {
if (DEBUG_WRITE_REGISTRY_DATA) {
File location = new File(".fabric" + File.separatorChar + "debug" + File.separatorChar + "registry");
boolean c = true;
if (!location.exists()) {
if (!location.mkdirs()) {
LOGGER.warn("[fabric-registry-sync debug] Could not create " + location.getAbsolutePath() + " directory!");
c = false;
}
}
MutableRegistry registry = Registry.REGISTRIES.get(registryId);
if (c && registry != null) {
File file = new File(location, registryId.toString().replace(':', '.').replace('/', '.') + ".csv");
try (FileOutputStream stream = new FileOutputStream(file)) {
StringBuilder builder = new StringBuilder("Raw ID,String ID,Class Type\n");
for (Object o : registry) {
String classType = (o == null) ? "null" : o.getClass().getName();
//noinspection unchecked
Identifier id = registry.getId(o);
if (id == null) continue;
//noinspection unchecked
int rawId = registry.getRawId(o);
String stringId = id.toString();
builder.append("\"").append(rawId).append("\",\"").append(stringId).append("\",\"").append(classType).append("\"\n");
}
stream.write(builder.toString().getBytes(StandardCharsets.UTF_8));
} catch (IOException e) {
LOGGER.warn("[fabric-registry-sync debug] Could not write to " + file.getAbsolutePath() + "!", e);
}
}
}
if (REGISTRY_BLACKLIST.contains(registryId)) {
continue;
} else if (isClientSync && REGISTRY_BLACKLIST_NETWORK.contains(registryId)) {
@ -87,12 +133,35 @@ public final class RegistrySyncManager {
}
MutableRegistry registry = Registry.REGISTRIES.get(registryId);
if (registry instanceof SimpleRegistry && registry instanceof RemappableRegistry) {
if (registry instanceof RemappableRegistry) {
CompoundTag registryTag = new CompoundTag();
//noinspection unchecked
for (Identifier identifier : (Set<Identifier>) registry.getIds()) {
registryTag.putInt(identifier.toString(), registry.getRawId(registry.get(identifier)));
IntSet rawIdsFound = DEBUG ? new IntOpenHashSet() : null;
for (Object o : registry) {
//noinspection unchecked
Identifier id = registry.getId(o);
if (id == null) continue;
//noinspection unchecked
int rawId = registry.getRawId(o);
if (DEBUG) {
if (registry.get(id) != o) {
LOGGER.error("[fabric-registry-sync] Inconsistency detected in " + registryId + ": object " + o + " -> string ID " + id + " -> object " + registry.get(id) + "!");
}
if (registry.get(rawId) != o) {
LOGGER.error("[fabric-registry-sync] Inconsistency detected in " + registryId + ": object " + o + " -> integer ID " + rawId + " -> object " + registry.get(rawId) + "!");
}
if (!rawIdsFound.add(rawId)) {
LOGGER.error("[fabric-registry-sync] Inconsistency detected in " + registryId + ": multiple objects hold the raw ID " + rawId + " (this one is " + id + ")");
}
}
registryTag.putInt(id.toString(), rawId);
}
mainTag.put(registryId.toString(), registryTag);
}
}
@ -106,22 +175,29 @@ public final class RegistrySyncManager {
public static void apply(CompoundTag tag, RemappableRegistry.RemapMode mode) throws RemapException {
CompoundTag mainTag = tag.getCompound("registries");
Set<String> containedRegistries = Sets.newHashSet(mainTag.getKeys());
for (Identifier registryId : Registry.REGISTRIES.getIds()) {
if (!mainTag.containsKey(registryId.toString())) {
if (!containedRegistries.remove(registryId.toString())) {
continue;
}
CompoundTag registryTag = mainTag.getCompound(registryId.toString());
MutableRegistry registry = Registry.REGISTRIES.get(registryId);
if (registry instanceof SimpleRegistry && registry instanceof RemappableRegistry) {
if (registry instanceof RemappableRegistry) {
Object2IntMap<Identifier> idMap = new Object2IntOpenHashMap<>();
for (String key : registryTag.getKeys()) {
idMap.put(new Identifier(key), registryTag.getInt(key));
}
((RemappableRegistry) registry).remap(registryId.toString(), idMap, mode);
}
}
if (!containedRegistries.isEmpty()) {
LOGGER.warn("[fabric-registry-sync] Could not find the following registries: " + Joiner.on(", ").join(containedRegistries));
}
}
public static void unmap() throws RemapException {

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.impl.registry;
import it.unimi.dsi.fastutil.ints.Int2IntMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import net.fabricmc.fabric.api.event.registry.RegistryIdRemapCallback;
import net.minecraft.util.Identifier;
import net.minecraft.util.registry.Registry;
public class RemapStateImpl<T> implements RegistryIdRemapCallback.RemapState<T> {
private final Int2IntMap rawIdChangeMap;
private final Int2ObjectMap<Identifier> oldIdMap;
private final Int2ObjectMap<Identifier> newIdMap;
public RemapStateImpl(Registry<T> registry, Int2ObjectMap<Identifier> oldIdMap, Int2IntMap rawIdChangeMap) {
this.rawIdChangeMap = rawIdChangeMap;
this.oldIdMap = oldIdMap;
this.newIdMap = new Int2ObjectOpenHashMap<>();
for (Int2IntMap.Entry entry : rawIdChangeMap.int2IntEntrySet()) {
Identifier id = registry.getId(registry.get(entry.getIntValue()));
newIdMap.put(entry.getIntValue(), id);
}
}
@Override
public Int2IntMap getRawIdChangeMap() {
return rawIdChangeMap;
}
@Override
public Identifier getIdFromOld(int oldRawId) {
return oldIdMap.get(oldRawId);
}
@Override
public Identifier getIdFromNew(int newRawId) {
return newIdMap.get(newRawId);
}
}

View file

@ -16,8 +16,12 @@
package net.fabricmc.fabric.impl.registry;
import it.unimi.dsi.fastutil.ints.Int2IntMap;
public interface RemovableIdList<T> {
void clear();
void remove(T o);
void removeId(int i);
void fabric_clear();
void fabric_remove(T o);
void fabric_removeId(int i);
void fabric_remapId(int from, int to);
void fabric_remapIds(Int2IntMap map);
}

View file

@ -16,10 +16,10 @@
package net.fabricmc.fabric.impl.registry.trackers;
import net.fabricmc.fabric.api.event.registry.RegistryEntryAddedCallback;
import net.fabricmc.fabric.api.event.registry.RegistryIdRemapCallback;
import net.fabricmc.fabric.api.event.registry.RegistryEntryRemovedCallback;
import net.fabricmc.fabric.impl.registry.RemovableIdList;
import net.fabricmc.fabric.impl.registry.ListenableRegistry;
import net.fabricmc.fabric.impl.registry.callbacks.RegistryPreClearCallback;
import net.fabricmc.fabric.impl.registry.callbacks.RegistryPreRegisterCallback;
import net.minecraft.util.IdList;
import net.minecraft.util.Identifier;
import net.minecraft.util.registry.Registry;
@ -27,40 +27,42 @@ import net.minecraft.util.registry.Registry;
import java.util.HashMap;
import java.util.Map;
public class IdListTracker<V, OV> implements RegistryPreClearCallback<V>, RegistryPreRegisterCallback<V> {
public class IdListTracker<V, OV> implements RegistryEntryAddedCallback<V>, RegistryIdRemapCallback<V>, RegistryEntryRemovedCallback<V> {
private final String name;
private final IdList<OV> mappers;
private final Registry<V> registry;
private Map<Identifier, OV> mapperCache = new HashMap<>();
private Map<Identifier, OV> removedMapperCache = new HashMap<>();
private IdListTracker(Registry<V> registry, IdList<OV> mappers) {
this.registry = registry;
private IdListTracker(String name, IdList<OV> mappers) {
this.name = name;
this.mappers = mappers;
}
public static <V, OV> void register(Registry<V> registry, IdList<OV> mappers) {
IdListTracker<V, OV> updater = new IdListTracker<>(registry, mappers);
((ListenableRegistry<V>) registry).getPreClearEvent().register(updater);
((ListenableRegistry<V>) registry).getPreRegisterEvent().register(updater);
public static <V, OV> void register(Registry<V> registry, String name, IdList<OV> mappers) {
IdListTracker<V, OV> updater = new IdListTracker<>(name, mappers);
RegistryEntryAddedCallback.event(registry).register(updater);
RegistryIdRemapCallback.event(registry).register(updater);
RegistryEntryRemovedCallback.event(registry).register(updater);
}
@Override
public void onPreClear() {
mapperCache.clear();
for (Identifier id : registry.getIds()) {
int rawId = registry.getRawId(registry.get(id));
OV mapper = mappers.get(rawId);
if (mapper != null) {
mapperCache.put(id, mapper);
}
public void onEntryAdded(int rawId, Identifier id, V object) {
if (removedMapperCache.containsKey(id)) {
mappers.set(removedMapperCache.get(id), rawId);
}
}
((RemovableIdList) mappers).clear();
@SuppressWarnings("unchecked")
@Override
public void onRemap(RemapState<V> state) {
((RemovableIdList<OV>) mappers).fabric_remapIds(state.getRawIdChangeMap());
}
@Override
public void onPreRegister(int id, Identifier identifier, V object, boolean isNewToRegistry) {
if (mapperCache.containsKey(identifier)) {
mappers.set(mapperCache.get(identifier), id);
public void onEntryRemoved(int rawId, Identifier id, V object) {
if (mappers.get(rawId) != null) {
removedMapperCache.put(id, mappers.get(rawId));
//noinspection unchecked
((RemovableIdList<OV>) mappers).fabric_removeId(rawId);
}
}
}

View file

@ -16,50 +16,83 @@
package net.fabricmc.fabric.impl.registry.trackers;
import com.google.common.base.Joiner;
import it.unimi.dsi.fastutil.ints.Int2IntMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import net.fabricmc.fabric.impl.registry.ListenableRegistry;
import net.fabricmc.fabric.impl.registry.callbacks.RegistryPreClearCallback;
import net.fabricmc.fabric.impl.registry.callbacks.RegistryPreRegisterCallback;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import net.fabricmc.fabric.api.event.registry.RegistryEntryAddedCallback;
import net.fabricmc.fabric.api.event.registry.RegistryIdRemapCallback;
import net.fabricmc.fabric.api.event.registry.RegistryEntryRemovedCallback;
import net.minecraft.util.Identifier;
import net.minecraft.util.registry.Registry;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class Int2ObjectMapTracker<V, OV> implements RegistryPreClearCallback<V>, RegistryPreRegisterCallback<V> {
public class Int2ObjectMapTracker<V, OV> implements RegistryEntryAddedCallback<V>, RegistryIdRemapCallback<V>, RegistryEntryRemovedCallback<V> {
private static final Logger LOGGER = LogManager.getLogger();
private final String name;
private final Int2ObjectMap<OV> mappers;
private final Registry<V> registry;
private Map<Identifier, OV> mapperCache = new HashMap<>();
private Map<Identifier, OV> removedMapperCache = new HashMap<>();
private Int2ObjectMapTracker(Registry<V> registry, Int2ObjectMap<OV> mappers) {
this.registry = registry;
private Int2ObjectMapTracker(String name, Int2ObjectMap<OV> mappers) {
this.name = name;
this.mappers = mappers;
}
public static <V, OV> void register(Registry<V> registry, Int2ObjectMap<OV> mappers) {
Int2ObjectMapTracker<V, OV> updater = new Int2ObjectMapTracker<>(registry, mappers);
((ListenableRegistry<V>) registry).getPreClearEvent().register(updater);
((ListenableRegistry<V>) registry).getPreRegisterEvent().register(updater);
public static <V, OV> void register(Registry<V> registry, String name, Int2ObjectMap<OV> mappers) {
Int2ObjectMapTracker<V, OV> updater = new Int2ObjectMapTracker<>(name, mappers);
RegistryEntryAddedCallback.event(registry).register(updater);
RegistryIdRemapCallback.event(registry).register(updater);
RegistryEntryRemovedCallback.event(registry).register(updater);
}
@Override
public void onPreClear() {
mapperCache.clear();
for (Identifier id : registry.getIds()) {
int rawId = registry.getRawId(registry.get(id));
OV mapper = mappers.get(rawId);
if (mapper != null) {
mapperCache.put(id, mapper);
public void onEntryAdded(int rawId, Identifier id, V object) {
if (removedMapperCache.containsKey(id)) {
mappers.put(rawId, removedMapperCache.get(id));
}
}
@Override
public void onRemap(RemapState<V> state) {
Int2ObjectMap<OV> oldMappers = new Int2ObjectOpenHashMap<>(mappers);
Int2IntMap remapMap = state.getRawIdChangeMap();
List<String> errors = null;
mappers.clear();
for (int i : oldMappers.keySet()) {
int newI = remapMap.getOrDefault(i, Integer.MIN_VALUE);
if (newI >= 0) {
if (mappers.containsKey(newI)) {
if (errors == null) {
errors = new ArrayList<>();
}
errors.add(" - Map contained two equal IDs " + newI + " (" + state.getIdFromOld(i) + "/" + i + " -> " + state.getIdFromNew(newI) + "/" + newI + ")!");
} else {
mappers.put(newI, oldMappers.get(i));
}
} else {
LOGGER.warn("[fabric-registry-sync] Int2ObjectMap " + name + " is dropping mapping for integer ID " + i + " (" + state.getIdFromOld(i) + ") - should not happen!");
removedMapperCache.put(state.getIdFromOld(i), oldMappers.get(i));
}
}
mappers.clear();
if (errors != null) {
throw new RuntimeException("Errors while remapping Int2ObjectMap " + name + " found:\n" + Joiner.on('\n').join(errors));
}
}
@Override
public void onPreRegister(int id, Identifier identifier, V object, boolean isNewToRegistry) {
if (mapperCache.containsKey(identifier)) {
mappers.put(id, mapperCache.get(identifier));
public void onEntryRemoved(int rawId, Identifier id, V object) {
OV mapper = mappers.remove(rawId);
if (mapper != null) {
removedMapperCache.put(id, mapper);
}
}
}

View file

@ -16,39 +16,78 @@
package net.fabricmc.fabric.impl.registry.trackers;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectRBTreeMap;
import net.fabricmc.fabric.api.event.registry.RegistryEntryAddedCallback;
import net.fabricmc.fabric.api.event.registry.RegistryIdRemapCallback;
import net.fabricmc.fabric.impl.registry.RemovableIdList;
import net.fabricmc.fabric.impl.registry.ListenableRegistry;
import net.fabricmc.fabric.impl.registry.callbacks.RegistryPostRegisterCallback;
import net.fabricmc.fabric.impl.registry.callbacks.RegistryPreClearCallback;
import net.minecraft.util.IdList;
import net.minecraft.util.Identifier;
import net.minecraft.util.registry.SimpleRegistry;
import net.minecraft.util.registry.Registry;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.util.Collection;
import java.util.function.Function;
public final class StateIdTracker<T, S> implements RegistryPreClearCallback<T>, RegistryPostRegisterCallback<T> {
public final class StateIdTracker<T, S> implements RegistryIdRemapCallback<T>, RegistryEntryAddedCallback<T> {
private final Logger logger = LogManager.getLogger();
private final Registry<T> registry;
private final IdList<S> stateList;
private final Function<T, Collection<S>> stateGetter;
private int currentHighestId = 0;
public static <T, S> void register(SimpleRegistry<T> registry, IdList<S> stateList, Function<T, Collection<S>> stateGetter) {
StateIdTracker<T, S> tracker = new StateIdTracker<>(stateList, stateGetter);
((ListenableRegistry<T>) registry).getPreClearEvent().register(tracker);
((ListenableRegistry<T>) registry).getPostRegisterEvent().register(tracker);
public static <T, S> void register(Registry<T> registry, IdList<S> stateList, Function<T, Collection<S>> stateGetter) {
StateIdTracker<T, S> tracker = new StateIdTracker<>(registry, stateList, stateGetter);
RegistryEntryAddedCallback.event(registry).register(tracker);
RegistryIdRemapCallback.event(registry).register(tracker);
}
private StateIdTracker(IdList<S> stateList, Function<T, Collection<S>> stateGetter) {
private StateIdTracker(Registry<T> registry, IdList<S> stateList, Function<T, Collection<S>> stateGetter) {
this.registry = registry;
this.stateList = stateList;
this.stateGetter = stateGetter;
recalcHighestId();
}
@Override
public void onPreClear() {
((RemovableIdList) stateList).clear();
public void onEntryAdded(int rawId, Identifier id, T object) {
if (rawId == currentHighestId + 1) {
stateGetter.apply(object).forEach(stateList::add);
currentHighestId = rawId;
} else {
logger.debug("[fabric-registry-sync] Non-sequential RegistryEntryAddedCallback for " + object.getClass().getSimpleName() + " ID tracker (at " + id + "), forcing state map recalculation...");
recalcStateMap();
}
}
@Override
public void onPostRegister(int rawId, Identifier id, T object) {
stateGetter.apply(object).forEach(stateList::add);
public void onRemap(RemapState<T> state) {
recalcStateMap();
}
private void recalcStateMap() {
((RemovableIdList) stateList).fabric_clear();
Int2ObjectMap<T> sortedBlocks = new Int2ObjectRBTreeMap<>();
currentHighestId = 0;
registry.forEach((t) -> {
int rawId = registry.getRawId(t);
currentHighestId = Math.max(currentHighestId, rawId);
sortedBlocks.put(rawId, t);
});
for (T b : sortedBlocks.values()) {
stateGetter.apply(b).forEach(stateList::add);
}
}
private void recalcHighestId() {
currentHighestId = 0;
for (T object : registry) {
currentHighestId = Math.max(currentHighestId, registry.getRawId(object));
}
}
}

View file

@ -16,15 +16,18 @@
package net.fabricmc.fabric.impl.registry.trackers.vanilla;
import it.unimi.dsi.fastutil.ints.Int2IntMap;
import net.fabricmc.fabric.api.event.registry.RegistryEntryAddedCallback;
import net.fabricmc.fabric.api.event.registry.RegistryIdRemapCallback;
import net.fabricmc.fabric.api.event.registry.RegistryEntryRemovedCallback;
import net.fabricmc.fabric.impl.registry.RemovableIdList;
import net.fabricmc.fabric.impl.registry.ListenableRegistry;
import net.fabricmc.fabric.impl.registry.callbacks.RegistryPostRegisterCallback;
import net.fabricmc.fabric.impl.registry.callbacks.RegistryPreClearCallback;
import net.minecraft.util.Identifier;
import net.minecraft.util.registry.Registry;
import net.minecraft.world.biome.Biome;
public final class BiomeParentTracker implements RegistryPreClearCallback<Biome>, RegistryPostRegisterCallback<Biome> {
import java.util.Objects;
public final class BiomeParentTracker implements RegistryEntryAddedCallback<Biome>, RegistryEntryRemovedCallback<Biome>, RegistryIdRemapCallback<Biome> {
private final Registry<Biome> registry;
private BiomeParentTracker(Registry<Biome> registry) {
@ -33,19 +36,32 @@ public final class BiomeParentTracker implements RegistryPreClearCallback<Biome>
public static void register(Registry<Biome> registry) {
BiomeParentTracker tracker = new BiomeParentTracker(registry);
((ListenableRegistry<Biome>) registry).getPreClearEvent().register(tracker);
((ListenableRegistry<Biome>) registry).getPostRegisterEvent().register(tracker);
RegistryEntryAddedCallback.event(registry).register(tracker);
RegistryIdRemapCallback.event(registry).register(tracker);
RegistryEntryRemovedCallback.event(registry).register(tracker);
}
@Override
public void onPostRegister(int rawId, Identifier id, Biome object) {
public void onEntryAdded(int rawId, Identifier id, Biome object) {
if (object.hasParent()) {
Biome.PARENT_BIOME_ID_MAP.set(object, registry.getRawId(registry.get(new Identifier(object.getParent()))));
Biome.PARENT_BIOME_ID_MAP.set(object, registry.getRawId(registry.get(new Identifier(Objects.requireNonNull(object.getParent())))));
}
}
@Override
public void onPreClear() {
((RemovableIdList) Biome.PARENT_BIOME_ID_MAP).clear();
public void onRemap(RemapState<Biome> state) {
for (Int2IntMap.Entry entry : state.getRawIdChangeMap().int2IntEntrySet()) {
if (Biome.PARENT_BIOME_ID_MAP.get(entry.getIntKey()) != null) {
//noinspection unchecked
((RemovableIdList<Biome>) Biome.PARENT_BIOME_ID_MAP).fabric_remapId(entry.getIntKey(), entry.getIntValue());
}
}
}
@SuppressWarnings("unchecked")
@Override
public void onEntryRemoved(int rawId, Identifier id, Biome object) {
((RemovableIdList<Biome>) Biome.PARENT_BIOME_ID_MAP).fabric_remove(object);
((RemovableIdList<Biome>) Biome.PARENT_BIOME_ID_MAP).fabric_removeId(rawId);
}
}

View file

@ -16,32 +16,31 @@
package net.fabricmc.fabric.impl.registry.trackers.vanilla;
import net.fabricmc.fabric.impl.registry.ListenableRegistry;
import net.fabricmc.fabric.impl.registry.callbacks.RegistryPostRegisterCallback;
import net.fabricmc.fabric.impl.registry.callbacks.RegistryPreClearCallback;
import net.fabricmc.fabric.impl.registry.callbacks.RegistryPreRegisterCallback;
import net.fabricmc.fabric.api.event.registry.RegistryEntryAddedCallback;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.item.BlockItem;
import net.minecraft.item.Item;
import net.minecraft.util.Identifier;
import net.minecraft.util.registry.SimpleRegistry;
import net.minecraft.util.registry.Registry;
public final class BlockInitTracker implements RegistryPreRegisterCallback<Block> {
private BlockInitTracker() {
public final class BlockInitTracker implements RegistryEntryAddedCallback<Block> {
private final Registry<Block> registry;
private BlockInitTracker(Registry<Block> registry) {
this.registry = registry;
}
public static void register(SimpleRegistry<Block> registry) {
BlockInitTracker tracker = new BlockInitTracker();
((ListenableRegistry<Block>) registry).getPreRegisterEvent().register(tracker);
public static void register(Registry<Block> registry) {
BlockInitTracker tracker = new BlockInitTracker(registry);
RegistryEntryAddedCallback.event(registry).register(tracker);
}
@Override
public void onPreRegister(int rawId, Identifier id, Block object, boolean isNewToRegistry) {
if (isNewToRegistry) {
object.getStateFactory().getStates().forEach(BlockState::initShapeCache);
object.getDropTableId();
}
public void onEntryAdded(int rawId, Identifier id, Block object) {
object.getStateFactory().getStates().forEach(BlockState::initShapeCache);
// if false, getDropTableId() will generate an invalid drop table ID
assert id.equals(registry.getId(object));
object.getDropTableId();
}
}

View file

@ -16,34 +16,26 @@
package net.fabricmc.fabric.impl.registry.trackers.vanilla;
import net.fabricmc.fabric.impl.registry.ListenableRegistry;
import net.fabricmc.fabric.impl.registry.callbacks.RegistryPostRegisterCallback;
import net.fabricmc.fabric.impl.registry.callbacks.RegistryPreClearCallback;
import net.fabricmc.fabric.api.event.registry.RegistryEntryAddedCallback;
import net.minecraft.item.BlockItem;
import net.minecraft.item.Item;
import net.minecraft.util.Identifier;
import net.minecraft.util.registry.SimpleRegistry;
import net.minecraft.util.registry.Registry;
public final class BlockItemTracker implements RegistryPreClearCallback<Item>, RegistryPostRegisterCallback<Item> {
public final class BlockItemTracker implements RegistryEntryAddedCallback<Item> {
private BlockItemTracker() {
}
public static void register(SimpleRegistry<Item> registry) {
public static void register(Registry<Item> registry) {
BlockItemTracker tracker = new BlockItemTracker();
((ListenableRegistry<Item>) registry).getPreClearEvent().register(tracker);
((ListenableRegistry<Item>) registry).getPostRegisterEvent().register(tracker);
RegistryEntryAddedCallback.event(registry).register(tracker);
}
@Override
public void onPostRegister(int rawId, Identifier id, Item object) {
public void onEntryAdded(int rawId, Identifier id, Item object) {
if (object instanceof BlockItem) {
((BlockItem) object).registerBlockItemMap(Item.BLOCK_ITEM_MAP, object);
((BlockItem) object).appendBlocks(Item.BLOCK_ITEMS, object);
}
}
@Override
public void onPreClear() {
Item.BLOCK_ITEM_MAP.clear();
}
}

View file

@ -16,14 +16,19 @@
package net.fabricmc.fabric.mixin.registry;
import it.unimi.dsi.fastutil.ints.Int2IntMap;
import it.unimi.dsi.fastutil.ints.Int2IntMaps;
import net.fabricmc.fabric.impl.registry.RemovableIdList;
import net.minecraft.util.IdList;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Set;
@Mixin(IdList.class)
public class MixinIdList implements RemovableIdList<Object> {
@ -35,7 +40,7 @@ public class MixinIdList implements RemovableIdList<Object> {
private List<Object> list;
@Override
public void clear() {
public void fabric_clear() {
nextId = 0;
idMap.clear();
list.clear();
@ -51,17 +56,55 @@ public class MixinIdList implements RemovableIdList<Object> {
}
@Override
public void remove(Object o) {
public void fabric_remove(Object o) {
if (idMap.containsKey(o)) {
fabric_removeInner(o);
}
}
@Override
public void removeId(int i) {
Object obj = list.get(i);
if (obj != null) {
fabric_removeInner(obj);
public void fabric_removeId(int i) {
List<Object> removals = new ArrayList<>();
for (Object o : idMap.keySet()) {
int j = idMap.get(o);
if (i == j) {
removals.add(o);
}
}
removals.forEach(this::fabric_removeInner);
}
@Override
public void fabric_remapId(int from, int to) {
fabric_remapIds(Int2IntMaps.singleton(from, to));
}
@Override
public void fabric_remapIds(Int2IntMap map) {
// remap idMap
idMap.replaceAll((a, b) -> map.get(b));
// remap list
nextId = 0;
List<Object> oldList = new ArrayList<>(list);
list.clear();
for (int k = 0; k < oldList.size(); k++) {
Object o = oldList.get(k);
if (o != null) {
int i = map.getOrDefault(k, k);
while (list.size() <= i) {
list.add(null);
}
list.set(i, o);
if (nextId <= i) {
nextId = i + 1;
}
}
}
}
}

View file

@ -18,16 +18,18 @@ package net.fabricmc.fabric.mixin.registry;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import it.unimi.dsi.fastutil.ints.*;
import it.unimi.dsi.fastutil.objects.Object2IntMap;
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
import net.fabricmc.fabric.api.event.Event;
import net.fabricmc.fabric.api.event.EventFactory;
import net.fabricmc.fabric.api.event.registry.RegistryEntryAddedCallback;
import net.fabricmc.fabric.api.event.registry.RegistryIdRemapCallback;
import net.fabricmc.fabric.api.event.registry.RegistryEntryRemovedCallback;
import net.fabricmc.fabric.impl.registry.ListenableRegistry;
import net.fabricmc.fabric.impl.registry.RemapStateImpl;
import net.fabricmc.fabric.impl.registry.RemapException;
import net.fabricmc.fabric.impl.registry.RemappableRegistry;
import net.fabricmc.fabric.impl.registry.callbacks.RegistryPostRegisterCallback;
import net.fabricmc.fabric.impl.registry.callbacks.RegistryPreClearCallback;
import net.fabricmc.fabric.impl.registry.callbacks.RegistryPreRegisterCallback;
import net.minecraft.util.Identifier;
import net.minecraft.util.Int2ObjectBiMap;
import net.minecraft.util.registry.SimpleRegistry;
@ -43,7 +45,7 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import java.util.*;
@Mixin(SimpleRegistry.class)
public abstract class MixinIdRegistry<T> implements RemappableRegistry, ListenableRegistry<T> {
public abstract class MixinIdRegistry<T> implements RemappableRegistry, ListenableRegistry {
@Shadow
protected Int2ObjectBiMap<T> indexedEntries;
@Shadow
@ -53,65 +55,95 @@ public abstract class MixinIdRegistry<T> implements RemappableRegistry, Listenab
@Unique
private static Logger FABRIC_LOGGER = LogManager.getLogger();
private final Event<RegistryPreClearCallback> fabric_preClearEvent = EventFactory.createArrayBacked(RegistryPreClearCallback.class,
(callbacks) -> () -> {
for (RegistryPreClearCallback callback : callbacks) {
callback.onPreClear();
}
}
);
private final Event<RegistryPreRegisterCallback> fabric_preRegisterEvent = EventFactory.createArrayBacked(RegistryPreRegisterCallback.class,
(callbacks) -> (a, b, c, d) -> {
for (RegistryPreRegisterCallback callback : callbacks) {
@Unique
private final Event<RegistryEntryAddedCallback> fabric_addObjectEvent = EventFactory.createArrayBacked(RegistryEntryAddedCallback.class,
(callbacks) -> (rawId, id, object) -> {
for (RegistryEntryAddedCallback callback : callbacks) {
//noinspection unchecked
callback.onPreRegister(a, b, c, d);
callback.onEntryAdded(rawId, id, object);
}
}
);
private final Event<RegistryPostRegisterCallback> fabric_postRegisterEvent = EventFactory.createArrayBacked(RegistryPostRegisterCallback.class,
(callbacks) -> (a, b, c) -> {
for (RegistryPostRegisterCallback callback : callbacks) {
@Unique
private final Event<RegistryEntryRemovedCallback> fabric_removeObjectEvent = EventFactory.createArrayBacked(RegistryEntryRemovedCallback.class,
(callbacks) -> (rawId, id, object) -> {
for (RegistryEntryRemovedCallback callback : callbacks) {
//noinspection unchecked
callback.onPostRegister(a, b, c);
callback.onEntryRemoved(rawId, id, object);
}
}
);
@Unique
private final Event<RegistryIdRemapCallback> fabric_postRemapEvent = EventFactory.createArrayBacked(RegistryIdRemapCallback.class,
(callbacks) -> (a) -> {
for (RegistryIdRemapCallback callback : callbacks) {
//noinspection unchecked
callback.onRemap(a);
}
}
);
@Unique
private Object2IntMap<Identifier> fabric_prevIndexedEntries;
@Unique
private BiMap<Identifier, T> fabric_prevEntries;
@Override
public Event<RegistryPreClearCallback<T>> getPreClearEvent() {
public Event<RegistryEntryAddedCallback<T>> fabric_getAddObjectEvent() {
//noinspection unchecked
return (Event<RegistryPreClearCallback<T>>) (Event) fabric_preClearEvent;
return (Event<RegistryEntryAddedCallback<T>>) (Event) fabric_addObjectEvent;
}
@Override
public Event<RegistryPreRegisterCallback<T>> getPreRegisterEvent() {
public Event<RegistryEntryRemovedCallback<T>> fabric_getRemoveObjectEvent() {
//noinspection unchecked
return (Event<RegistryPreRegisterCallback<T>>) (Event) fabric_preRegisterEvent;
return (Event<RegistryEntryRemovedCallback<T>>) (Event) fabric_removeObjectEvent;
}
@Override
public Event<RegistryPostRegisterCallback<T>> getPostRegisterEvent() {
public Event<RegistryIdRemapCallback<T>> fabric_getRemapEvent() {
//noinspection unchecked
return (Event<RegistryPostRegisterCallback<T>>) (Event) fabric_postRegisterEvent;
return (Event<RegistryIdRemapCallback<T>>) (Event) fabric_postRemapEvent;
}
// The rest of the registry isn't thread-safe, so this one need not be either.
@Unique
private boolean fabric_isObjectNew = false;
@SuppressWarnings({"unchecked", "ConstantConditions"})
@Inject(method = "set", at = @At("HEAD"))
public void setPre(int id, Identifier identifier, Object object, CallbackInfoReturnable info) {
boolean isNewToRegistry = !entries.containsKey(identifier);
fabric_preRegisterEvent.invoker().onPreRegister(id, identifier, object, isNewToRegistry);
int indexedEntriesId = indexedEntries.getId((T) object);
if (indexedEntriesId >= 0) {
throw new RuntimeException("Attempted to register object " + object + " twice! (at raw IDs " + indexedEntriesId + " and " + id + " )");
}
if (!entries.containsKey(identifier)) {
fabric_isObjectNew = true;
} else {
T oldObject = entries.get(identifier);
if (oldObject != null && oldObject != object) {
int oldId = indexedEntries.getId(oldObject);
if (oldId != id) {
throw new RuntimeException("Attempted to register ID " + identifier + " at different raw IDs (" + oldId + ", " + id + ")! If you're trying to override an item, use .set(), not .register()!");
}
fabric_removeObjectEvent.invoker().onEntryRemoved(oldId, identifier, oldObject);
fabric_isObjectNew = true;
} else {
fabric_isObjectNew = false;
}
}
}
@SuppressWarnings({"unchecked", "ConstantConditions"})
@SuppressWarnings("unchecked")
@Inject(method = "set", at = @At("RETURN"))
public void setPost(int id, Identifier identifier, Object object, CallbackInfoReturnable info) {
SimpleRegistry<Object> registry = (SimpleRegistry<Object>) (Object) this;
fabric_postRegisterEvent.invoker().onPostRegister(id, identifier, object);
if (fabric_isObjectNew) {
fabric_addObjectEvent.invoker().onEntryAdded(id, identifier, object);
}
}
@Override
@ -124,14 +156,18 @@ public abstract class MixinIdRegistry<T> implements RemappableRegistry, Listenab
case AUTHORITATIVE:
break;
case REMOTE: {
List<String> strings = new ArrayList<>();
List<String> strings = null;
for (Identifier remoteId : remoteIndexedEntries.keySet()) {
if (!registry.getIds().contains(remoteId)) {
if (!entries.keySet().contains(remoteId)) {
if (strings == null) {
strings = new ArrayList<>();
}
strings.add(" - " + remoteId);
}
}
if (!strings.isEmpty()) {
if (strings != null) {
StringBuilder builder = new StringBuilder("Received ID map for " + name + " contains IDs unknown to the receiver!");
for (String s : strings) {
builder.append('\n').append(s);
@ -140,10 +176,10 @@ public abstract class MixinIdRegistry<T> implements RemappableRegistry, Listenab
}
} break;
case EXACT: {
if (!registry.getIds().equals(remoteIndexedEntries.keySet())) {
if (!entries.keySet().equals(remoteIndexedEntries.keySet())) {
List<String> strings = new ArrayList<>();
for (Identifier remoteId : remoteIndexedEntries.keySet()) {
if (!registry.getIds().contains(remoteId)) {
if (!entries.keySet().contains(remoteId)) {
strings.add(" - " + remoteId + " (missing on local)");
}
}
@ -172,46 +208,69 @@ public abstract class MixinIdRegistry<T> implements RemappableRegistry, Listenab
if (fabric_prevIndexedEntries == null) {
fabric_prevIndexedEntries = new Object2IntOpenHashMap<>();
fabric_prevEntries = HashBiMap.create(entries);
for (Identifier id : registry.getIds()) {
//noinspection unchecked
fabric_prevIndexedEntries.put(id, registry.getRawId(registry.get(id)));
for (Object o : registry) {
fabric_prevIndexedEntries.put(registry.getId(o), registry.getRawId(o));
}
}
Int2ObjectMap<Identifier> oldIdMap = new Int2ObjectOpenHashMap<>();
for (Object o : registry) {
oldIdMap.put(registry.getRawId(o), registry.getId(o));
}
// If we're AUTHORITATIVE, we append entries which only exist on the
// local side to the new entry list. For REMOTE, we instead drop them.
if (mode == RemapMode.AUTHORITATIVE) {
int maxValue = 0;
switch (mode) {
case AUTHORITATIVE: {
int maxValue = 0;
Object2IntMap<Identifier> oldRemoteIndexedEntries = remoteIndexedEntries;
remoteIndexedEntries = new Object2IntOpenHashMap<>();
for (Identifier id : oldRemoteIndexedEntries.keySet()) {
int v = oldRemoteIndexedEntries.getInt(id);
remoteIndexedEntries.put(id, v);
if (v > maxValue) maxValue = v;
}
for (Identifier id : registry.getIds()) {
if (!remoteIndexedEntries.containsKey(id)) {
FABRIC_LOGGER.warn("Adding " + id + " to registry.");
remoteIndexedEntries.put(id, ++maxValue);
Object2IntMap<Identifier> oldRemoteIndexedEntries = remoteIndexedEntries;
remoteIndexedEntries = new Object2IntOpenHashMap<>();
for (Identifier id : oldRemoteIndexedEntries.keySet()) {
int v = oldRemoteIndexedEntries.getInt(id);
remoteIndexedEntries.put(id, v);
if (v > maxValue) maxValue = v;
}
}
} else if (mode == RemapMode.REMOTE) {
// TODO: Is this what mods really want?
Set<Identifier> droppedIds = new HashSet<>();
for (Identifier id : registry.getIds()) {
if (!remoteIndexedEntries.containsKey(id)) {
droppedIds.add(id);
for (Identifier id : registry.getIds()) {
if (!remoteIndexedEntries.containsKey(id)) {
FABRIC_LOGGER.warn("Adding " + id + " to saved/remote registry.");
remoteIndexedEntries.put(id, ++maxValue);
}
}
}
} break;
case REMOTE: {
// TODO: Is this what mods really want?
Set<Identifier> droppedIds = new HashSet<>();
entries.keySet().removeAll(droppedIds);
for (Identifier id : registry.getIds()) {
if (!remoteIndexedEntries.containsKey(id)) {
Object object = registry.get(id);
int rid = registry.getRawId(object);
droppedIds.add(id);
// Emit RemoveObject events for removed objects.
//noinspection unchecked
fabric_getRemoveObjectEvent().invoker().onEntryRemoved(rid, id, (T) object);
}
}
// note: indexedEntries cannot be safely remove()d from
entries.keySet().removeAll(droppedIds);
} break;
}
// Inform about registry clearing.
fabric_preClearEvent.invoker().onPreClear();
Int2IntMap idMap = new Int2IntOpenHashMap();
for (Object o : indexedEntries) {
Identifier id = registry.getId(o);
int rid = registry.getRawId(o);
// see above note
if (remoteIndexedEntries.containsKey(id)) {
idMap.put(rid, remoteIndexedEntries.getInt(id));
}
}
// entries was handled above, if it was necessary.
indexedEntries.clear();
@ -235,29 +294,40 @@ public abstract class MixinIdRegistry<T> implements RemappableRegistry, Listenab
}
continue;
}
//noinspection unchecked
fabric_preRegisterEvent.invoker().onPreRegister(id, identifier, object, false);
// Add the new object, increment nextId to match.
indexedEntries.put(object, id);
if (nextId <= id) {
nextId = id + 1;
}
//noinspection unchecked
fabric_postRegisterEvent.invoker().onPostRegister(id, identifier, object);
}
//noinspection unchecked
fabric_getRemapEvent().invoker().onRemap(new RemapStateImpl(registry, oldIdMap, idMap));
}
@Override
public void unmap(String name) throws RemapException {
if (fabric_prevIndexedEntries != null) {
List<Identifier> addedIds = new ArrayList<>();
// Emit AddObject events for previously culled objects.
for (Identifier id : fabric_prevEntries.keySet()) {
if (!entries.containsKey(id)) {
assert fabric_prevIndexedEntries.containsKey(id);
addedIds.add(id);
}
}
entries.clear();
entries.putAll(fabric_prevEntries);
remap(name, fabric_prevIndexedEntries, RemapMode.AUTHORITATIVE);
for (Identifier id : addedIds) {
fabric_getAddObjectEvent().invoker().onEntryAdded(indexedEntries.getId(entries.get(id)), id, entries.get(id));
}
fabric_prevIndexedEntries = null;
fabric_prevEntries = null;
}

View file

@ -30,23 +30,24 @@ 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.callback.CallbackInfo;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.*;
@Mixin(WorldSaveHandler.class)
public class MixinWorldSaveHandler {
@Unique
private static final int FABRIC_ID_REGISTRY_BACKUPS = 3;
@Unique
private static Logger FABRIC_LOGGER = LogManager.getLogger();
@Shadow
public File worldDir;
@Unique
private CompoundTag fabric_lastSavedIdMap = null;
@Unique
private boolean fabric_readIdMapFile(File file) throws IOException, RemapException {
if (file.exists()) {
FileInputStream fileInputStream = new FileInputStream(file);
@ -61,20 +62,69 @@ public class MixinWorldSaveHandler {
return false;
}
private File getWorldIdMapFile(int i) {
@Unique
private File fabric_getWorldIdMapFile(int i) {
return new File(new File(worldDir, "data"), "fabricRegistry" + ".dat" + (i == 0 ? "" : ("." + i)));
}
@Unique
private void fabric_saveRegistryData() {
CompoundTag newIdMap = RegistrySyncManager.toTag(false);
if (!newIdMap.equals(fabric_lastSavedIdMap)) {
for (int i = FABRIC_ID_REGISTRY_BACKUPS - 1; i >= 0; i--) {
File file = fabric_getWorldIdMapFile(i);
if (file.exists()) {
if (i == FABRIC_ID_REGISTRY_BACKUPS - 1) {
file.delete();
} else {
File target = fabric_getWorldIdMapFile(i + 1);
file.renameTo(target);
}
}
}
try {
File file = fabric_getWorldIdMapFile(0);
File parentFile = file.getParentFile();
if (!parentFile.exists()) {
if (!parentFile.mkdirs()) {
FABRIC_LOGGER.warn("[fabric-registry-sync] Could not create directory " + parentFile + "!");
}
}
FileOutputStream fileOutputStream = new FileOutputStream(file);
NbtIo.writeCompressed(newIdMap, fileOutputStream);
fileOutputStream.close();
} catch (IOException e) {
FABRIC_LOGGER.warn("[fabric-registry-sync] Failed to save registry file!", e);
}
fabric_lastSavedIdMap = newIdMap;
}
}
@Inject(method = "saveWorld", at = @At("HEAD"))
public void saveWorld(LevelProperties levelProperties, CompoundTag compoundTag, CallbackInfo info) {
if (!worldDir.exists()) {
return;
}
fabric_saveRegistryData();
}
// TODO: stop double save on client?
@Inject(method = "readProperties", at = @At("HEAD"))
public void readWorldProperties(CallbackInfoReturnable<LevelProperties> callbackInfo) {
// Load
for (int i = 0; i < FABRIC_ID_REGISTRY_BACKUPS; i++) {
FABRIC_LOGGER.info("Loading Fabric registry [file " + (i + 1) + "/" + (FABRIC_ID_REGISTRY_BACKUPS + 1) + "]");
FABRIC_LOGGER.trace("[fabric-registry-sync] Loading Fabric registry [file " + (i + 1) + "/" + (FABRIC_ID_REGISTRY_BACKUPS + 1) + "]");
try {
if (fabric_readIdMapFile(getWorldIdMapFile(i))) {
break;
if (fabric_readIdMapFile(fabric_getWorldIdMapFile(i))) {
FABRIC_LOGGER.info("[fabric-registry-sync] Loaded registry data [file " + (i + 1) + "/" + (FABRIC_ID_REGISTRY_BACKUPS + 1) + "]");
return;
}
} catch (FileNotFoundException e) {
// pass
} catch (IOException e) {
if (i >= FABRIC_ID_REGISTRY_BACKUPS - 1) {
throw new RuntimeException(e);
@ -86,29 +136,7 @@ public class MixinWorldSaveHandler {
}
}
CompoundTag newIdMap = RegistrySyncManager.toTag(false);
if (!newIdMap.equals(fabric_lastSavedIdMap)) {
for (int i = FABRIC_ID_REGISTRY_BACKUPS - 1; i >= 0; i--) {
File file = getWorldIdMapFile(i);
if (file.exists()) {
if (i == FABRIC_ID_REGISTRY_BACKUPS - 1) {
file.delete();
} else {
File target = getWorldIdMapFile(i + 1);
file.renameTo(target);
}
}
}
try {
FileOutputStream fileOutputStream = new FileOutputStream(getWorldIdMapFile(0));
NbtIo.writeCompressed(newIdMap, fileOutputStream);
fileOutputStream.close();
} catch (IOException e) {
FABRIC_LOGGER.warn("Failed to save registry file!", e);
}
fabric_lastSavedIdMap = newIdMap;
}
// If not returned (not present), try saving the registry data
fabric_saveRegistryData();
}
}

View file

@ -34,6 +34,6 @@ public class MixinBlockColorMap {
@Inject(method = "<init>", at = @At("RETURN"))
private void create(CallbackInfo info) {
IdListTracker.register(Registry.BLOCK, providers);
IdListTracker.register(Registry.BLOCK, "BlockColors.providers", providers);
}
}

View file

@ -34,6 +34,6 @@ public class MixinItemColorMap {
@Inject(method = "<init>", at = @At("RETURN"))
private void create(CallbackInfo info) {
IdListTracker.register(Registry.ITEM, providers);
IdListTracker.register(Registry.ITEM, "ItemColors.providers", providers);
}
}

View file

@ -38,7 +38,7 @@ public class MixinItemModelMap {
@Inject(method = "<init>", at = @At("RETURN"))
public void onInit(BakedModelManager bakedModelManager, CallbackInfo info) {
Int2ObjectMapTracker.register(Registry.ITEM, modelIds);
Int2ObjectMapTracker.register(Registry.ITEM, models);
Int2ObjectMapTracker.register(Registry.ITEM, "ItemModels.modelIds", modelIds);
Int2ObjectMapTracker.register(Registry.ITEM, "ItemModels.models", models);
}
}

View file

@ -36,6 +36,6 @@ public class MixinParticleManager {
@Inject(method = "<init>", at = @At("RETURN"))
public void onInit(World world, TextureManager textureManager, CallbackInfo info) {
Int2ObjectMapTracker.register(Registry.PARTICLE_TYPE, factories);
Int2ObjectMapTracker.register(Registry.PARTICLE_TYPE, "ParticleManager.factories", factories);
}
}

View file

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

View file

@ -21,6 +21,7 @@ import net.fabricmc.fabric.api.renderer.v1.render.RenderContext;
import net.minecraft.client.texture.Sprite;
import net.minecraft.client.util.math.Vector3f;
import net.minecraft.util.math.Direction;
import net.minecraft.util.math.MathHelper;
/**
* Specialized {@link MutableQuadView} obtained via {@link MeshBuilder#getEmitter()}
@ -99,17 +100,31 @@ public interface QuadEmitter extends MutableQuadView {
@Override
QuadEmitter spriteBake(int spriteIndex, Sprite sprite, int bakeFlags);
/**
* Tolerance for determining if the depth parameter to {@link #square(Direction, float, float, float, float, float)}
* is effectively zero - meaning the face is a cull face.
*/
final float CULL_FACE_EPSILON = 0.00001f;
/**
* Helper method to assign vertex coordinates for a square aligned with the given face.
* Ensures that vertex order is consistent with vanilla convention. (Incorrect order can
* lead to bad AO lighting.)<p>
* lead to bad AO lighting unless enhanced lighting logic is available/enabled.)<p>
*
* Square will be parallel to the given face and coplanar with the face if depth == 0.
* All coordinates are normalized (0-1).
* Square will be parallel to the given face and coplanar with the face (and culled if the
* face is occluded) if the depth parameter is approximately zero. See {@link #CULL_FACE_EPSILON}.<p>
*
* All coordinates should be normalized (0-1).
*/
default QuadEmitter square(Direction nominalFace, float left, float bottom, float right, float top, float depth) {
cullFace(depth == 0 ? nominalFace : null);
if(Math.abs(depth) < CULL_FACE_EPSILON) {
cullFace(nominalFace);
depth = 0; // avoid any inconsistency for face quads
} else {
cullFace(null);
}
nominalFace(nominalFace);
switch(nominalFace)
{

View file

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

View file

@ -34,8 +34,15 @@ import java.util.Properties;
public class Indigo implements ClientModInitializer {
public static final boolean ALWAYS_TESSELATE_INDIGO;
public static final boolean ENSURE_VERTEX_FORMAT_COMPATIBILITY;
public static final AoConfig AMBIENT_OCCLUSION_MODE;
private static final Logger LOGGER = LogManager.getLogger();
/** Set true in dev env to confirm results match vanilla when they should */
public static final boolean DEBUG_COMPARE_LIGHTING;
public static final boolean FIX_SMOOTH_LIGHTING_OFFSET;
public static final boolean FIX_EXTERIOR_VERTEX_LIGHTING;
public static final boolean FIX_LUMINOUS_AO_SHADE;
public static final Logger LOGGER = LogManager.getLogger();
private static boolean asBoolean(String property, boolean defValue) {
switch (asTriState(property)) {
@ -48,7 +55,8 @@ public class Indigo implements ClientModInitializer {
}
}
private static <T extends Enum> T asEnum(String property, T defValue) {
@SuppressWarnings({ "rawtypes", "unchecked" })
private static <T extends Enum> T asEnum(String property, T defValue) {
if (property == null || property.isEmpty()) {
return defValue;
} else {
@ -97,9 +105,16 @@ public class Indigo implements ClientModInitializer {
}
}
ALWAYS_TESSELATE_INDIGO = asBoolean((String) properties.computeIfAbsent("always-tesselate-blocks", (a) -> "auto"), true);
AMBIENT_OCCLUSION_MODE = asEnum((String) properties.computeIfAbsent("ambient-occlusion-mode", (a) -> "enhanced"), AoConfig.ENHANCED);
final boolean forceCompatibility = IndigoMixinConfigPlugin.shouldForceCompatibility();
ENSURE_VERTEX_FORMAT_COMPATIBILITY = forceCompatibility;
// necessary because OF alters the BakedModel vertex format and will confuse the fallback model consumer
ALWAYS_TESSELATE_INDIGO = !forceCompatibility && asBoolean((String) properties.computeIfAbsent("always-tesselate-blocks", (a) -> "auto"), true);
AMBIENT_OCCLUSION_MODE = asEnum((String) properties.computeIfAbsent("ambient-occlusion-mode", (a) -> "hybrid"), AoConfig.HYBRID);
DEBUG_COMPARE_LIGHTING = asBoolean((String) properties.computeIfAbsent("debug-compare-lighting", (a) -> "auto"), false);
FIX_SMOOTH_LIGHTING_OFFSET = asBoolean((String) properties.computeIfAbsent("fix-smooth-lighting-offset", (a) -> "auto"), true);
FIX_EXTERIOR_VERTEX_LIGHTING = asBoolean((String) properties.computeIfAbsent("fix-exterior-vertex-lighting", (a) -> "auto"), true);
FIX_LUMINOUS_AO_SHADE = asBoolean((String) properties.computeIfAbsent("fix-luminous-block-ambient-occlusion", (a) -> "auto"), false);
try (FileOutputStream stream = new FileOutputStream(configFile)) {
properties.store(stream, "Indigo properties file");
} catch (IOException e) {
@ -111,6 +126,9 @@ public class Indigo implements ClientModInitializer {
public void onInitializeClient() {
if (IndigoMixinConfigPlugin.shouldApplyIndigo()) {
LOGGER.info("[Indigo] Registering Indigo renderer!");
if(IndigoMixinConfigPlugin.shouldForceCompatibility()) {
LOGGER.info("[Indigo] Compatibility mode enabled.");
}
RendererAccess.INSTANCE.registerRenderer(IndigoRenderer.INSTANCE);
} else {
LOGGER.info("[Indigo] Different rendering plugin detected; not applying Indigo.");

View file

@ -18,6 +18,8 @@ package net.fabricmc.indigo;
import net.fabricmc.loader.api.FabricLoader;
import net.fabricmc.loader.api.ModContainer;
import net.fabricmc.loader.api.metadata.ModMetadata;
import org.spongepowered.asm.lib.tree.ClassNode;
import org.spongepowered.asm.mixin.extensibility.IMixinConfigPlugin;
import org.spongepowered.asm.mixin.extensibility.IMixinInfo;
@ -26,23 +28,39 @@ import java.util.List;
import java.util.Set;
public class IndigoMixinConfigPlugin implements IMixinConfigPlugin {
private static final String JSON_ELEMENT = "fabric-renderer-api-v1:contains_renderer";
private static Boolean indigoApplicable;
/** Set by other renderers to disable loading of Indigo */
private static final String JSON_KEY_DISABLE_INDIGO = "fabric-renderer-api-v1:contains_renderer";
/** Disables vanilla block tesselation and ensures vertex format compatibility */
private static final String JSON_KEY_FORCE_COMPATIBILITY = "fabric-renderer-indigo:force_compatibility";
private static boolean needsLoad = true;
private static boolean indigoApplicable = true;
private static boolean forceCompatibility = false;
private static void loadIfNeeded() {
if(needsLoad) {
for (ModContainer container : FabricLoader.getInstance().getAllMods()) {
final ModMetadata meta = container.getMetadata();
if (meta.containsCustomElement(JSON_KEY_DISABLE_INDIGO)) {
indigoApplicable = false;
} else if (meta.containsCustomElement(JSON_KEY_FORCE_COMPATIBILITY)) {
forceCompatibility = true;
}
}
needsLoad = false;
}
}
static boolean shouldApplyIndigo() {
if (indigoApplicable != null) return indigoApplicable;
for (ModContainer container : FabricLoader.getInstance().getAllMods()) {
if (container.getMetadata().containsCustomElement(JSON_ELEMENT)) {
indigoApplicable = false;
return false;
}
}
indigoApplicable = true;
return true;
loadIfNeeded();
return indigoApplicable;
}
static boolean shouldForceCompatibility() {
loadIfNeeded();
return forceCompatibility;
}
@Override
public void onLoad(String mixinPackage) {

View file

@ -41,7 +41,7 @@ public abstract class RenderMaterialImpl {
private static final int TEXTURE_DEPTH_MASK = 3;
private static final int TEXTURE_DEPTH_SHIFT = 0;
private static final int BLEND_MODE_MASK = 3;
private static final int BLEND_MODE_MASK = 7;
private static final int[] BLEND_MODE_SHIFT = new int[3];
private static final int[] COLOR_DISABLE_FLAGS = new int[3];
private static final int[] EMISSIVE_FLAGS = new int[3];

View file

@ -16,6 +16,8 @@
package net.fabricmc.indigo.renderer.accessor;
import net.fabricmc.indigo.renderer.mesh.QuadViewImpl;
public interface AccessBufferBuilder {
void fabric_putVanillaData(int[] data, int start, boolean isItemFormat);
void fabric_putQuad(QuadViewImpl quad);
}

View file

@ -74,8 +74,6 @@ public class AoCalculator {
}
private static final Logger LOGGER = LogManager.getLogger();
// TODO: make this actually configurable?
private static final boolean fixSmoothLighting = true;
private final VanillaAoCalc vanillaCalc;
private final BlockPos.Mutable lightPos = new BlockPos.Mutable();
@ -112,37 +110,39 @@ public class AoCalculator {
completionFlags = 0;
}
/** Set true in dev env to confirm results match vanilla when they should */
private static final boolean DEBUG = Boolean.valueOf(System.getProperty("fabric.debugAoLighting", "false"));
public void compute(MutableQuadViewImpl quad, boolean isVanilla) {
final AoConfig config = Indigo.AMBIENT_OCCLUSION_MODE;
boolean shouldMatch = false;
final boolean shouldCompare;
switch(config) {
case VANILLA:
calcVanilla(quad);
// no point in comparing vanilla with itself
shouldCompare = false;
break;
case EMULATE:
calcFastVanilla(quad);
shouldMatch = DEBUG && isVanilla;
shouldCompare = Indigo.DEBUG_COMPARE_LIGHTING && isVanilla;
break;
default:
case HYBRID:
if(isVanilla) {
shouldCompare = Indigo.DEBUG_COMPARE_LIGHTING;
calcFastVanilla(quad);
break;
} else {
shouldCompare = false;
calcEnhanced(quad);
}
// else fall through to enhanced
break;
default:
case ENHANCED:
shouldMatch = calcEnhanced(quad);
shouldCompare = false;
calcEnhanced(quad);
}
if (shouldMatch) {
if (shouldCompare) {
float[] vanillaAo = new float[4];
int[] vanillaLight = new int[4];
@ -164,7 +164,7 @@ public class AoCalculator {
}
}
}
private void calcVanilla(MutableQuadViewImpl quad) {
vanillaCalc.compute(blockInfo, quad, ao, light);
}
@ -173,61 +173,41 @@ public class AoCalculator {
int flags = quad.geometryFlags();
// force to block face if shape is full cube - matches vanilla logic
if(((flags & LIGHT_FACE_FLAG) == 0) && Block.isShapeFullCube(blockInfo.blockState.getCollisionShape(blockInfo.blockView, blockInfo.blockPos))) {
if((flags & LIGHT_FACE_FLAG) == 0 && (flags & AXIS_ALIGNED_FLAG) == AXIS_ALIGNED_FLAG && Block.isShapeFullCube(blockInfo.blockState.getCollisionShape(blockInfo.blockView, blockInfo.blockPos))) {
flags |= LIGHT_FACE_FLAG;
}
switch(flags) {
case AXIS_ALIGNED_FLAG | CUBIC_FLAG | LIGHT_FACE_FLAG:
vanillaFullFace(quad, true);
break;
case AXIS_ALIGNED_FLAG | LIGHT_FACE_FLAG:
vanillaPartialFace(quad, true);
break;
case AXIS_ALIGNED_FLAG | CUBIC_FLAG:
vanillaFullFace(quad, false);
break;
default:
case AXIS_ALIGNED_FLAG:
vanillaPartialFace(quad, false);
break;
}
if((flags & CUBIC_FLAG) == 0) {
vanillaPartialFace(quad, (flags & LIGHT_FACE_FLAG) != 0);
} else {
vanillaFullFace(quad, (flags & LIGHT_FACE_FLAG) != 0);
}
}
/** returns true if should match vanilla results */
private boolean calcEnhanced(MutableQuadViewImpl quad) {
private void calcEnhanced(MutableQuadViewImpl quad) {
switch(quad.geometryFlags()) {
case AXIS_ALIGNED_FLAG | CUBIC_FLAG | LIGHT_FACE_FLAG:
vanillaFullFace(quad, true);
return DEBUG;
case AXIS_ALIGNED_FLAG | LIGHT_FACE_FLAG:
vanillaPartialFace(quad, true);
return DEBUG;
break;
case AXIS_ALIGNED_FLAG | CUBIC_FLAG:
blendedFullFace(quad);
return false;
case AXIS_ALIGNED_FLAG:
blendedPartialFace(quad);
return false;
break;
default:
irregularFace(quad);
return false;
break;
}
}
private void vanillaFullFace(MutableQuadViewImpl quad, boolean isOnLightFace) {
private void vanillaFullFace(QuadViewImpl quad, boolean isOnLightFace) {
final Direction lightFace = quad.lightFace();
computeFace(lightFace, isOnLightFace).toArray(ao, light, VERTEX_MAP[lightFace.getId()]);
}
private void vanillaPartialFace(MutableQuadViewImpl quad, boolean isOnLightFace) {
private void vanillaPartialFace(QuadViewImpl quad, boolean isOnLightFace) {
final Direction lightFace = quad.lightFace();
AoFaceData faceData = computeFace(lightFace, isOnLightFace);
final WeightFunction wFunc = AoFace.get(lightFace).weightFunc;
@ -239,7 +219,7 @@ public class AoCalculator {
}
}
/** used in {@link #blendedInsetFace(VertexEditorImpl, Direction)} as return variable to avoid new allocation */
/** used in {@link #blendedInsetFace(QuadViewImpl quad, int vertexIndex, Direction lightFace)} as return variable to avoid new allocation */
AoFaceData tmpFace = new AoFaceData();
/** Returns linearly interpolated blend of outer and inner face based on depth of vertex in face */
@ -250,7 +230,7 @@ public class AoCalculator {
}
/**
* Like {@link #blendedInsetFace(VertexEditorImpl, Direction)} but optimizes if depth is 0 or 1.
* Like {@link #blendedInsetFace(QuadViewImpl quad, int vertexIndex, Direction lightFace)} but optimizes if depth is 0 or 1.
* Used for irregular faces when depth varies by vertex to avoid unneeded interpolation.
*/
private AoFaceData gatherInsetFace(QuadViewImpl quad, int vertexIndex, Direction lightFace) {
@ -265,12 +245,7 @@ public class AoCalculator {
}
}
private void blendedFullFace(MutableQuadViewImpl quad) {
final Direction lightFace = quad.lightFace();
blendedInsetFace(quad, 0, lightFace).toArray(ao, light, VERTEX_MAP[lightFace.getId()]);
}
private void blendedPartialFace(MutableQuadViewImpl quad) {
private void blendedPartialFace(QuadViewImpl quad) {
final Direction lightFace = quad.lightFace();
AoFaceData faceData = blendedInsetFace(quad, 0, lightFace);
final WeightFunction wFunc = AoFace.get(lightFace).weightFunc;
@ -302,12 +277,15 @@ public class AoCalculator {
final AoFaceData fd = gatherInsetFace(quad, i, face);
AoFace.get(face).weightFunc.apply(quad, i, w);
final float n = x * x;
ao += n * fd.weigtedAo(w);
sky += n * fd.weigtedSkyLight(w);
block += n * fd.weigtedBlockLight(w);
maxAo = fd.maxAo(maxAo);
maxSky = fd.maxSkyLight(maxSky);
maxBlock = fd.maxBlockLight(maxBlock);
final float a = fd.weigtedAo(w);
final int s = fd.weigtedSkyLight(w);
final int b = fd.weigtedBlockLight(w);
ao += n * a;
sky += n * s;
block += n * b;
maxAo = a;
maxSky = s;
maxBlock = b;
}
final float y = normal.y();
@ -316,12 +294,15 @@ public class AoCalculator {
final AoFaceData fd = gatherInsetFace(quad, i, face);
AoFace.get(face).weightFunc.apply(quad, i, w);
final float n = y * y;
ao += n * fd.weigtedAo(w);
sky += n * fd.weigtedSkyLight(w);
block += n * fd.weigtedBlockLight(w);
maxAo = fd.maxAo(maxAo);
maxSky = fd.maxSkyLight(maxSky);
maxBlock = fd.maxBlockLight(maxBlock);
final float a = fd.weigtedAo(w);
final int s = fd.weigtedSkyLight(w);
final int b = fd.weigtedBlockLight(w);
ao += n * a;
sky += n * s;
block += n * b;
maxAo = Math.max(maxAo, a);
maxSky = Math.max(maxSky, s);
maxBlock = Math.max(maxBlock, b);
}
final float z = normal.z();
@ -330,12 +311,15 @@ public class AoCalculator {
final AoFaceData fd = gatherInsetFace(quad, i, face);
AoFace.get(face).weightFunc.apply(quad, i, w);
final float n = z * z;
ao += n * fd.weigtedAo(w);
sky += n * fd.weigtedSkyLight(w);
block += n * fd.weigtedBlockLight(w);
maxAo = fd.maxAo(maxAo);
maxSky = fd.maxSkyLight(maxSky);
maxBlock = fd.maxBlockLight(maxBlock);
final float a = fd.weigtedAo(w);
final int s = fd.weigtedSkyLight(w);
final int b = fd.weigtedBlockLight(w);
ao += n * a;
sky += n * s;
block += n * b;
maxAo = Math.max(maxAo, a);
maxSky = Math.max(maxSky, s);
maxBlock = Math.max(maxBlock, b);
}
aoResult[i] = (ao + maxAo) * 0.5f;
@ -383,16 +367,16 @@ public class AoCalculator {
// vanilla was further offsetting these in the direction of the light face
// but it was actually mis-sampling and causing visible artifacts in certain situation
searchPos.set(lightPos).setOffset(aoFace.neighbors[0]);//.setOffset(lightFace);
if(!fixSmoothLighting) searchPos.setOffset(lightFace);
if(!Indigo.FIX_SMOOTH_LIGHTING_OFFSET) searchPos.setOffset(lightFace);
final boolean isClear0 = world.getBlockState(searchPos).getLightSubtracted(world, searchPos) == 0;
searchPos.set(lightPos).setOffset(aoFace.neighbors[1]);//.setOffset(lightFace);
if(!fixSmoothLighting) searchPos.setOffset(lightFace);
if(!Indigo.FIX_SMOOTH_LIGHTING_OFFSET) searchPos.setOffset(lightFace);
final boolean isClear1 = world.getBlockState(searchPos).getLightSubtracted(world, searchPos) == 0;
searchPos.set(lightPos).setOffset(aoFace.neighbors[2]);//.setOffset(lightFace);
if(!fixSmoothLighting) searchPos.setOffset(lightFace);
if(!Indigo.FIX_SMOOTH_LIGHTING_OFFSET) searchPos.setOffset(lightFace);
final boolean isClear2 = world.getBlockState(searchPos).getLightSubtracted(world, searchPos) == 0;
searchPos.set(lightPos).setOffset(aoFace.neighbors[3]);//.setOffset(lightFace);
if(!fixSmoothLighting) searchPos.setOffset(lightFace);
if(!Indigo.FIX_SMOOTH_LIGHTING_OFFSET) searchPos.setOffset(lightFace);
final boolean isClear3 = world.getBlockState(searchPos).getLightSubtracted(world, searchPos) == 0;
// c = corner - values at corners of face
@ -469,7 +453,7 @@ public class AoCalculator {
* value from all four samples.
*/
private static int meanBrightness(int a, int b, int c, int d) {
if(fixSmoothLighting) {
if(Indigo.FIX_SMOOTH_LIGHTING_OFFSET) {
return a == 0 || b == 0 || c == 0 || d == 0 ? meanEdgeBrightness(a, b, c, d) : meanInnerBrightness(a, b, c, d);
} else {
return vanillaMeanBrightness(a, b, c, d);

View file

@ -36,8 +36,7 @@ public enum AoConfig {
* aligned quads not on the block face will have interpolated brightness based
* on depth instead of the all-or-nothing brightness of vanilla.<p>
*
* Unit (full face) quads must still have the vanilla fixed winding order but smaller
* quads can have vertices in any (counter-clockwise) order.<p>
* Non-vanilla quads can have vertices in any (counter-clockwise) order.<p>
*/
ENHANCED,

View file

@ -17,7 +17,7 @@
package net.fabricmc.indigo.renderer.aocalc;
import static net.minecraft.util.math.Direction.*;
import static net.fabricmc.indigo.renderer.aocalc.AoVertexClampFunction.CLAMP_FUNC;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.minecraft.util.SystemUtil;
@ -30,55 +30,55 @@ import net.fabricmc.indigo.renderer.mesh.QuadViewImpl;
*/
@Environment(EnvType.CLIENT)
enum AoFace {
AOF_DOWN(new Direction[]{WEST, EAST, NORTH, SOUTH}, (q, i) -> q.y(i),
AOF_DOWN(new Direction[]{WEST, EAST, NORTH, SOUTH}, (q, i) -> CLAMP_FUNC.clamp(q.y(i)),
(q, i, w) -> {
final float u = q.x(i);
final float v = q.z(i);
final float u = CLAMP_FUNC.clamp(q.x(i));
final float v = CLAMP_FUNC.clamp(q.z(i));
w[0] = (1-u) * v;
w[1] = (1-u) * (1-v);
w[2] = u * (1-v);
w[3] = u * v;
}),
AOF_UP(new Direction[]{EAST, WEST, NORTH, SOUTH}, (q, i) -> 1 - q.y(i),
AOF_UP(new Direction[]{EAST, WEST, NORTH, SOUTH}, (q, i) -> 1 - CLAMP_FUNC.clamp(q.y(i)),
(q, i, w) -> {
final float u = q.x(i);
final float v = q.z(i);
final float u = CLAMP_FUNC.clamp(q.x(i));
final float v = CLAMP_FUNC.clamp(q.z(i));
w[0] = u * v;
w[1] = u * (1-v);
w[2] = (1-u) * (1-v);
w[3] = (1-u) * v;
}),
AOF_NORTH(new Direction[]{UP, DOWN, EAST, WEST}, (q, i) -> q.z(i),
AOF_NORTH(new Direction[]{UP, DOWN, EAST, WEST}, (q, i) -> CLAMP_FUNC.clamp(q.z(i)),
(q, i, w) -> {
final float u = q.y(i);
final float v = q.x(i);
final float u = CLAMP_FUNC.clamp(q.y(i));
final float v = CLAMP_FUNC.clamp(q.x(i));
w[0] = u * (1-v);
w[1] = u * v;
w[2] = (1-u) * v;
w[3] = (1-u) * (1-v);
}),
AOF_SOUTH(new Direction[]{WEST, EAST, DOWN, UP}, (q, i) -> 1 - q.z(i),
AOF_SOUTH(new Direction[]{WEST, EAST, DOWN, UP}, (q, i) -> 1 - CLAMP_FUNC.clamp(q.z(i)),
(q, i, w) -> {
final float u = q.y(i);
final float v = q.x(i);
final float u = CLAMP_FUNC.clamp(q.y(i));
final float v = CLAMP_FUNC.clamp(q.x(i));
w[0] = u * (1-v);
w[1] = (1-u) * (1-v);
w[2] = (1-u) * v;
w[3] = u * v;
}),
AOF_WEST(new Direction[]{UP, DOWN, NORTH, SOUTH}, (q, i) -> q.x(i),
AOF_WEST(new Direction[]{UP, DOWN, NORTH, SOUTH}, (q, i) -> CLAMP_FUNC.clamp(q.x(i)),
(q, i, w) -> {
final float u = q.y(i);
final float v = q.z(i);
final float u = CLAMP_FUNC.clamp(q.y(i));
final float v = CLAMP_FUNC.clamp(q.z(i));
w[0] = u * v;
w[1] = u * (1-v);
w[2] = (1-u) * (1-v);
w[3] = (1-u) * v;
}),
AOF_EAST(new Direction[]{DOWN, UP, NORTH, SOUTH}, (q, i) -> 1 - q.x(i),
AOF_EAST(new Direction[]{DOWN, UP, NORTH, SOUTH}, (q, i) -> 1 - CLAMP_FUNC.clamp(q.x(i)),
(q, i, w) -> {
final float u = q.y(i);
final float v = q.z(i);
final float u = CLAMP_FUNC.clamp(q.y(i));
final float v = CLAMP_FUNC.clamp(q.z(i));
w[0] = (1-u) * v;
w[1] = (1-u) * (1-v);
w[2] = u * (1-v);

View file

@ -58,22 +58,10 @@ class AoFaceData {
return (int) (b0 * w[0] + b1 * w[1] + b2 * w[2] + b3 * w[3]) & 0xFF;
}
int maxBlockLight(int oldMax) {
final int i = b0 > b1 ? b0 : b1;
final int j = b2 > b3 ? b2 : b3;
return Math.max(oldMax, i > j ? i : j);
}
int weigtedSkyLight(float[] w) {
return (int) (s0 * w[0] + s1 * w[1] + s2 * w[2] + s3 * w[3]) & 0xFF;
}
int maxSkyLight(int oldMax) {
final int i = s0 > s1 ? s0 : s1;
final int j = s2 > s3 ? s2 : s3;
return Math.max(oldMax, i > j ? i : j);
}
int weightedCombinedLight(float[] w) {
return weigtedSkyLight(w) << 16 | weigtedBlockLight(w);
}
@ -82,13 +70,6 @@ class AoFaceData {
return a0 * w[0] + a1 * w[1] + a2 * w[2] + a3 * w[3];
}
float maxAo(float oldMax) {
final float x = a0 > a1 ? a0 : a1;
final float y = a2 > a3 ? a2 : a3;
final float z = x > y ? x : y;
return oldMax > z ? oldMax : z;
}
void toArray(float[] aOut, int[] bOut, int[] vertexMap) {
aOut[vertexMap[0]] = a0;
aOut[vertexMap[1]] = a1;

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.indigo.renderer.aocalc;
import net.fabricmc.indigo.Indigo;
import net.minecraft.block.BlockState;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.BlockView;
/**
* Implements a fix to prevent luminous blocks from casting AO shade.
* Will give normal result if fix is disabled.
*/
@FunctionalInterface
public interface AoLuminanceFix {
float apply(BlockView view, BlockPos pos);
AoLuminanceFix INSTANCE = Indigo.FIX_LUMINOUS_AO_SHADE ? AoLuminanceFix::fixed : AoLuminanceFix::vanilla;
static float vanilla(BlockView view, BlockPos pos) {
return view.getBlockState(pos).getAmbientOcclusionLightLevel(view, pos);
}
static float fixed(BlockView view, BlockPos pos) {
final BlockState state = view.getBlockState(pos);
return state.getLuminance() == 0 ? state.getAmbientOcclusionLightLevel(view, pos) : 1f;
}
}

View file

@ -0,0 +1,29 @@
/*
* 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.indigo.renderer.aocalc;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.fabricmc.indigo.Indigo;
@Environment(EnvType.CLIENT)
@FunctionalInterface
interface AoVertexClampFunction {
float clamp(float x);
AoVertexClampFunction CLAMP_FUNC = Indigo.FIX_EXTERIOR_VERTEX_LIGHTING ? x -> x < 0f ? 0f : (x > 1f ? 1f : x) : x -> x;
}

View file

@ -1,120 +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.indigo.renderer.helper;
import com.google.common.collect.Sets;
import it.unimi.dsi.fastutil.objects.Object2IntMap;
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
import net.minecraft.client.render.VertexFormat;
import net.minecraft.client.render.VertexFormatElement;
import net.minecraft.client.render.VertexFormats;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
public class BufferBuilderTransformHelper {
/**
* Fast copying mode; used only if the vanilla format is an exact match.
*/
public static final int MODE_COPY_FAST = 0;
/**
* Padded copying mode; used when the vanilla format is an exact match,
* but includes additional data at the end. Will emit a warning.
*/
public static final int MODE_COPY_PADDED = 1;
/**
* ShadersMod compatibility mode; as MODE_COPY_PADDED, but populates in
* the correct normal values as provided by the mod.
*
* Assumes a format of [vertex, color, texture, lmap, normal], all in
* their respective vanilla formats, plus any amount of additional data
* afterwards.
*/
public static final int MODE_COPY_PADDED_SHADERSMOD = 2;
/**
* Unsupported mode; an error will be emitted and no quads will be
* pushed to the buffer builder.
*/
public static final int MODE_UNSUPPORTED = 3;
private static final Map<VertexFormat, Integer> vertexFormatCache = new ConcurrentHashMap<>();
private static final Set<VertexFormat> errorEmittedFormats = Sets.newConcurrentHashSet();
private static final Logger logger = LogManager.getLogger();
public static void emitUnsupportedError(VertexFormat format) {
// This can be slow, as it's only called on unsupported formats - which is already an error condition.
if (errorEmittedFormats.add(format)) {
logger.error("[Indigo] Unsupported vertex format! " + format);
}
}
private static int computeProcessingMode(VertexFormat f) {
if (
f.getElementCount() >= 4 && f.getVertexSizeInteger() >= 7
&& f.getElement(0).equals(VertexFormats.POSITION_ELEMENT)
&& f.getElement(1).equals(VertexFormats.COLOR_ELEMENT)
&& f.getElement(2).equals(VertexFormats.UV_ELEMENT)
) {
if (
f.getElement(3).equals(VertexFormats.LMAP_ELEMENT)
|| f.getElement(3).equals(VertexFormats.NORMAL_ELEMENT)
) {
if (
f.getElementCount() >= 5
&& f.getElement(3).equals(VertexFormats.LMAP_ELEMENT)
&& f.getElement(4).equals(VertexFormats.NORMAL_ELEMENT)
) {
logger.debug("[Indigo] Classified format as ShadersMod-compatible: " + f);
return MODE_COPY_PADDED_SHADERSMOD;
} else if (f.getElementCount() == 4) {
logger.debug("[Indigo] Classified format as vanilla-like: " + f);
return MODE_COPY_FAST;
} else {
logger.debug("[Indigo] Unsupported but likely vanilla-compliant vertex format. " + f);
return MODE_COPY_PADDED;
}
}
}
return MODE_UNSUPPORTED;
}
public static int getProcessingMode(VertexFormat format) {
// Fast passthrough for the most common vanilla block/item formats.
if (format == VertexFormats.POSITION_COLOR_UV_LMAP || format == VertexFormats.POSITION_COLOR_UV_NORMAL) {
return MODE_COPY_FAST;
} else {
Integer cached = vertexFormatCache.get(format);
if (cached == null) {
// VertexFormats are mutable, so we need to make an immutable copy.
format = new VertexFormat(format);
cached = computeProcessingMode(format);
vertexFormatCache.put(format, cached);
}
return cached;
}
}
}

View file

@ -39,7 +39,10 @@ public abstract class GeometryHelper {
/** set when a quad is coplanar with its light face. Implies {@link #AXIS_ALIGNED_FLAG} */
public static final int LIGHT_FACE_FLAG = AXIS_ALIGNED_FLAG << 1;
private static final float EPS_MIN = 0.0001f;
private static final float EPS_MAX = 1.0f - EPS_MIN;
private GeometryHelper() {}
/**
@ -56,10 +59,10 @@ public abstract class GeometryHelper {
if(isParallelQuadOnFace(lightFace, quad)) {
bits |= LIGHT_FACE_FLAG;
}
if(isQuadCubic(lightFace, quad)) {
bits |= CUBIC_FLAG;
}
}
if(isQuadCubic(lightFace, quad)) {
bits |= CUBIC_FLAG;
}
return bits;
}
@ -80,17 +83,17 @@ public abstract class GeometryHelper {
}
/**
* True if quad - already known to be parallel to a face - is actually coplanar with it.<p>
* True if quad - already known to be parallel to a face - is actually coplanar with it.
* For compatibility with vanilla resource packs, also true if quad is outside the face.<p>
*
* Test will be unreliable if not already parallel, use {@link #isQuadParallel(Direction, QuadView)}
* Test will be unreliable if not already parallel, use {@link #isQuadParallelToFace(Direction, QuadView)}
* for that purpose. Expects convex quads with all points co-planar.<p>
*/
public static boolean isParallelQuadOnFace(Direction lightFace, QuadView quad) {
if(lightFace == null)
return false;
final int coordinateIndex = lightFace.getAxis().ordinal();
final float expectedValue = lightFace.getDirection() == AxisDirection.POSITIVE ? 1 : 0;
return equalsApproximate(quad.posByIndex(0, coordinateIndex), expectedValue);
final float x = quad.posByIndex(0, lightFace.getAxis().ordinal());
return lightFace.getDirection() == AxisDirection.POSITIVE ? x >= EPS_MAX : x <= EPS_MIN;
}
/**
@ -101,7 +104,7 @@ public abstract class GeometryHelper {
* quad vertices are coplanar with each other. <p>
*
* Expects convex quads with all points co-planar.<p>
*
*
* @param lightFace MUST be non-null.
*/
public static boolean isQuadCubic(Direction lightFace, QuadView quad) {
@ -134,10 +137,13 @@ public abstract class GeometryHelper {
return confirmSquareCorners(a, b, quad);
}
/**
* Used by {@link #isQuadCubic(Direction, int[], int, QuadSerializer)}.
* True if quad touches all four corners of unit square.
* Used by {@link #isQuadCubic(Direction, QuadView)}.
* True if quad touches all four corners of unit square.<p>
*
* For compatibility with resource packs that contain models with quads exceeding
* block boundaries, considers corners outside the block to be at the corners.
*/
private static boolean confirmSquareCorners(int aCoordinate, int bCoordinate, QuadView quad) {
int flags = 0;
@ -146,18 +152,18 @@ public abstract class GeometryHelper {
final float a = quad.posByIndex(i, aCoordinate);
final float b = quad.posByIndex(i, bCoordinate);
if(equalsApproximate(a, 0)) {
if(equalsApproximate(b, 0)) {
if(a <= EPS_MIN) {
if(b <= EPS_MIN) {
flags |= 1;
} else if(equalsApproximate(b, 1)) {
} else if(b >= EPS_MAX) {
flags |= 2;
} else {
return false;
}
} else if(equalsApproximate(a, 1)) {
if(equalsApproximate(b, 0)) {
} else if(a >= EPS_MAX) {
if(b <= EPS_MIN) {
flags |= 4;
} else if(equalsApproximate(b, 1)) {
} else if(b >= EPS_MAX) {
flags |= 8;
} else {
return false;

View file

@ -50,6 +50,7 @@ public class MeshBuilderImpl implements MeshBuilder {
int[] packed = new int[index];
System.arraycopy(data, 0, packed, 0, index);
index = 0;
maker.begin(data, index);
return new MeshImpl(packed);
}

View file

@ -18,18 +18,15 @@ package net.fabricmc.indigo.renderer.mixin;
import java.nio.IntBuffer;
import net.fabricmc.indigo.Indigo;
import net.fabricmc.indigo.renderer.helper.BufferBuilderTransformHelper;
import net.fabricmc.indigo.renderer.mesh.EncodingFormat;
import net.minecraft.client.render.VertexFormat;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import net.fabricmc.indigo.Indigo;
import net.fabricmc.indigo.renderer.accessor.AccessBufferBuilder;
import net.fabricmc.indigo.renderer.mesh.QuadViewImpl;
import net.minecraft.client.render.BufferBuilder;
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.client.render.VertexFormat;
import net.minecraft.client.render.VertexFormatElement;
@Mixin(BufferBuilder.class)
public abstract class MixinBufferBuilder implements AccessBufferBuilder {
@ -40,73 +37,71 @@ public abstract class MixinBufferBuilder implements AccessBufferBuilder {
@Shadow public abstract VertexFormat getVertexFormat();
private static final int VERTEX_STRIDE_INTS = 7;
private static final int VERTEX_STRIDE_BYTES = VERTEX_STRIDE_INTS * 4;
private static final int QUAD_STRIDE_INTS = VERTEX_STRIDE_INTS * 4;
private static final int QUAD_STRIDE_BYTES = QUAD_STRIDE_INTS * 4;
private int fabric_processingMode;
@Override
public void fabric_putQuad(QuadViewImpl quad) {
if(Indigo.ENSURE_VERTEX_FORMAT_COMPATIBILITY) {
bufferCompatibly(quad);
} else {
bufferFast(quad);
}
}
@Inject(at = @At("RETURN"), method = "begin")
private void afterBegin(int mode, VertexFormat passedFormat, CallbackInfo info) {
fabric_processingMode = BufferBuilderTransformHelper.getProcessingMode(getVertexFormat());
}
private void bufferFast(QuadViewImpl quad) {
grow(QUAD_STRIDE_BYTES);
bufInt.position(getCurrentSize());
bufInt.put(quad.data(), quad.vertexStart(), QUAD_STRIDE_INTS);
vertexCount += 4;
}
/**
* Similar to {@link BufferBuilder#putVertexData(int[])} but
* accepts an array index so that arrays containing more than one
* quad don't have to be copied to a transfer array before the call.
*
* It also always assumes the vanilla data format and is capable of
* transforming data from it to a different, non-vanilla data format.
* Uses buffer vertex format to drive buffer population.
* Relies on logic elsewhere to ensure coordinates don't include chunk offset
* (because buffer builder will handle that.)<p>
*
* Calling putVertexData() would likely be a little faster but this approach
* gives us a chance to pass vertex normals to shaders, which isn't possible
* with the standard block format. It also doesn't require us to encode a specific
* custom format directly, which would be prone to breakage outside our control.
*/
@Override
public void fabric_putVanillaData(int[] data, int start, boolean isItemFormat) {
switch (fabric_processingMode) {
case BufferBuilderTransformHelper.MODE_COPY_FAST: {
this.grow(QUAD_STRIDE_BYTES);
this.bufInt.position(this.getCurrentSize());
this.bufInt.put(data, start, QUAD_STRIDE_INTS);
} break;
case BufferBuilderTransformHelper.MODE_COPY_PADDED: {
int currSize = this.getCurrentSize();
int formatSizeBytes = getVertexFormat().getVertexSize();
int formatSizeInts = formatSizeBytes / 4;
this.grow(formatSizeBytes * 4);
private void bufferCompatibly(QuadViewImpl quad) {
final VertexFormat format = getVertexFormat();;
final int elementCount = format.getElementCount();
for(int i = 0; i < 4; i++) {
for(int j = 0; j < elementCount; j++) {
VertexFormatElement e = format.getElement(j);
switch(e.getType()) {
case COLOR:
final int c = quad.spriteColor(i, 0);
((BufferBuilder)(Object)this).color(c & 0xFF, (c >>> 8) & 0xFF, (c >>> 16) & 0xFF, (c >>> 24) & 0xFF);
break;
case NORMAL:
((BufferBuilder)(Object)this).normal(quad.normalX(i), quad.normalY(i), quad.normalZ(i));
break;
case POSITION:
((BufferBuilder)(Object)this).vertex(quad.x(i), quad.y(i), quad.z(i));
break;
case UV:
if(e.getIndex() == 0) {
((BufferBuilder)(Object)this).texture(quad.spriteU(i, 0), quad.spriteV(i, 0));
} else {
final int b = quad.lightmap(i);
((BufferBuilder)(Object)this).texture((b >> 16) & 0xFFFF, b & 0xFFFF);
}
break;
this.bufInt.position(currSize);
this.bufInt.put(data, start, VERTEX_STRIDE_INTS);
this.bufInt.position(currSize + formatSizeInts);
this.bufInt.put(data, start + 7, VERTEX_STRIDE_INTS);
this.bufInt.position(currSize + formatSizeInts * 2);
this.bufInt.put(data, start + 14, VERTEX_STRIDE_INTS);
this.bufInt.position(currSize + formatSizeInts * 3);
this.bufInt.put(data, start + 21, VERTEX_STRIDE_INTS);
} break;
case BufferBuilderTransformHelper.MODE_COPY_PADDED_SHADERSMOD: {
int currSize = this.getCurrentSize();
int formatSizeBytes = getVertexFormat().getVertexSize();
int formatSizeInts = formatSizeBytes / 4;
this.grow(formatSizeBytes * 4);
// these types should never occur and/or require no action
case MATRIX:
case BLEND_WEIGHT:
case PADDING:
default:
break;
this.bufInt.position(currSize);
this.bufInt.put(data, start, VERTEX_STRIDE_INTS);
this.bufInt.put(data[start + EncodingFormat.NORMALS_OFFSET_VANILLA]);
this.bufInt.position(currSize + formatSizeInts);
this.bufInt.put(data, start + 7, VERTEX_STRIDE_INTS);
this.bufInt.put(data[start + EncodingFormat.NORMALS_OFFSET_VANILLA + 1]);
this.bufInt.position(currSize + formatSizeInts * 2);
this.bufInt.put(data, start + 14, VERTEX_STRIDE_INTS);
this.bufInt.put(data[start + EncodingFormat.NORMALS_OFFSET_VANILLA + 2]);
this.bufInt.position(currSize + formatSizeInts * 3);
this.bufInt.put(data, start + 21, VERTEX_STRIDE_INTS);
this.bufInt.put(data[start + EncodingFormat.NORMALS_OFFSET_VANILLA + 3]);
} break;
case BufferBuilderTransformHelper.MODE_UNSUPPORTED:
// Don't emit any quads.
BufferBuilderTransformHelper.emitUnsupportedError(getVertexFormat());
return;
}
this.vertexCount += 4;
}
}
((BufferBuilder)(Object)this).next();
}
}
}

View file

@ -28,6 +28,8 @@ import net.fabricmc.indigo.renderer.RenderMaterialImpl.Value;
import net.fabricmc.indigo.renderer.IndigoRenderer;
import net.fabricmc.indigo.renderer.accessor.AccessBufferBuilder;
import net.fabricmc.indigo.renderer.aocalc.AoCalculator;
import net.fabricmc.indigo.renderer.helper.ColorHelper;
import net.fabricmc.indigo.renderer.helper.GeometryHelper;
import net.fabricmc.indigo.renderer.mesh.EncodingFormat;
import net.fabricmc.indigo.renderer.mesh.MeshImpl;
import net.fabricmc.indigo.renderer.mesh.MutableQuadViewImpl;
@ -57,6 +59,8 @@ public abstract class AbstractMeshConsumer extends AbstractQuadRenderer implemen
// only used via RenderContext.getEmitter()
@Override
public Maker emit() {
lightFace = GeometryHelper.lightFace(this);
ColorHelper.applyDiffuseShading(this, false);
renderQuad(this);
clear();
return this;

View file

@ -27,6 +27,7 @@ import net.fabricmc.indigo.renderer.aocalc.AoCalculator;
import net.fabricmc.indigo.renderer.helper.ColorHelper;
import net.fabricmc.indigo.renderer.mesh.EncodingFormat;
import net.fabricmc.indigo.renderer.mesh.MutableQuadViewImpl;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.util.math.BlockPos;
@ -68,7 +69,7 @@ public abstract class AbstractQuadRenderer {
/** final output step, common to all renders */
private void bufferQuad(MutableQuadViewImpl quad, int renderLayer) {
bufferFunc.get(renderLayer).fabric_putVanillaData(quad.data(), quad.vertexStart(), false);
bufferFunc.get(renderLayer).fabric_putQuad(quad);
}
// routines below have a bit of copy-paste code reuse to avoid conditional execution inside a hot loop
@ -140,7 +141,7 @@ public abstract class AbstractQuadRenderer {
*/
int flatBrightness(MutableQuadViewImpl quad, BlockState blockState, BlockPos pos) {
mpos.set(pos);
if((quad.geometryFlags() & LIGHT_FACE_FLAG) != 0) {
if((quad.geometryFlags() & LIGHT_FACE_FLAG) != 0 || Block.isShapeFullCube(blockState.getCollisionShape(blockInfo.blockView, pos))) {
mpos.setOffset(quad.lightFace());
}
return brightnessFunc.applyAsInt(blockState, mpos);

View file

@ -27,6 +27,7 @@ import net.fabricmc.fabric.api.renderer.v1.model.FabricBakedModel;
import net.fabricmc.fabric.api.renderer.v1.render.RenderContext;
import net.fabricmc.indigo.renderer.accessor.AccessBufferBuilder;
import net.fabricmc.indigo.renderer.aocalc.AoCalculator;
import net.fabricmc.indigo.renderer.aocalc.AoLuminanceFix;
import net.fabricmc.indigo.renderer.mesh.MutableQuadViewImpl;
import net.fabricmc.indigo.renderer.mixin.BufferBuilderOffsetAccessor;
import net.minecraft.block.BlockState;
@ -67,10 +68,7 @@ public class BlockRenderContext extends AbstractRenderContext implements RenderC
private float aoLevel(BlockPos pos) {
final ExtendedBlockView blockView = blockInfo.blockView;
if(blockView == null) {
return 1f;
}
return blockView.getBlockState(pos).getAmbientOcclusionLightLevel(blockView, pos);
return blockView == null ? 1f : AoLuminanceFix.INSTANCE.apply(blockView, pos);
}
private AccessBufferBuilder outputBuffer(int renderLayer) {

View file

@ -18,8 +18,10 @@ package net.fabricmc.indigo.renderer.render;
import it.unimi.dsi.fastutil.longs.Long2FloatOpenHashMap;
import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap;
import net.fabricmc.indigo.Indigo;
import net.fabricmc.indigo.renderer.accessor.AccessBufferBuilder;
import net.fabricmc.indigo.renderer.accessor.AccessChunkRenderer;
import net.fabricmc.indigo.renderer.aocalc.AoLuminanceFix;
import net.fabricmc.indigo.renderer.mesh.MutableQuadViewImpl;
import net.minecraft.block.Block.OffsetType;
import net.minecraft.block.BlockRenderLayer;
@ -67,6 +69,7 @@ public class ChunkRenderInfo {
private final Long2FloatOpenHashMap aoLevelCache;
private final BlockRenderInfo blockInfo;
private final BlockPos.Mutable chunkOrigin = new BlockPos.Mutable();
ChunkRenderTask chunkTask;
ChunkRenderData chunkData;
ChunkRenderer chunkRenderer;
@ -102,6 +105,7 @@ public class ChunkRenderInfo {
}
void prepare(ChunkRenderer chunkRenderer, BlockPos.Mutable chunkOrigin, boolean [] resultFlags) {
this.chunkOrigin.set(chunkOrigin);
this.chunkData = chunkTask.getRenderData();
this.chunkRenderer = chunkRenderer;
this.resultFlags = resultFlags;
@ -129,10 +133,19 @@ public class ChunkRenderInfo {
void beginBlock() {
final BlockState blockState = blockInfo.blockState;
final BlockPos blockPos = blockInfo.blockPos;
offsetX = (float) (chunkOffsetX + blockPos.getX());
offsetY = (float) (chunkOffsetY + blockPos.getY());
offsetZ = (float) (chunkOffsetZ + blockPos.getZ());
// When we are using the BufferBuilder input methods, the builder will
// add the chunk offset for us, so we should only apply the block offset.
if(Indigo.ENSURE_VERTEX_FORMAT_COMPATIBILITY) {
offsetX = (float) (blockPos.getX());
offsetY = (float) (blockPos.getY());
offsetZ = (float) (blockPos.getZ());
} else {
offsetX = (float) (chunkOffsetX + blockPos.getX());
offsetY = (float) (chunkOffsetY + blockPos.getY());
offsetZ = (float) (chunkOffsetZ + blockPos.getZ());
}
if(blockState.getBlock().getOffsetType() != OffsetType.NONE) {
Vec3d offset = blockState.getOffsetPos(blockInfo.blockView, blockPos);
offsetX += (float)offset.x;
@ -153,7 +166,7 @@ public class ChunkRenderInfo {
BlockRenderLayer layer = LAYERS[layerIndex];
if (!chunkData.isBufferInitialized(layer)) {
chunkData.markBufferInitialized(layer); // start buffer
((AccessChunkRenderer) chunkRenderer).fabric_beginBufferBuilding(builder, blockInfo.blockPos);
((AccessChunkRenderer) chunkRenderer).fabric_beginBufferBuilding(builder, chunkOrigin);
}
result = (AccessBufferBuilder) builder;
}
@ -188,7 +201,7 @@ public class ChunkRenderInfo {
long key = pos.asLong();
float result = aoLevelCache.get(key);
if (result == Float.MAX_VALUE) {
result = blockView.getBlockState(pos).getAmbientOcclusionLightLevel(blockView, pos);
result = AoLuminanceFix.INSTANCE.apply(blockView, pos);
aoLevelCache.put(key, result);
}
return result;

View file

@ -33,6 +33,7 @@ import net.fabricmc.fabric.api.renderer.v1.render.RenderContext;
import net.fabricmc.indigo.renderer.RenderMaterialImpl;
import net.fabricmc.indigo.renderer.accessor.AccessBufferBuilder;
import net.fabricmc.indigo.renderer.helper.ColorHelper;
import net.fabricmc.indigo.renderer.helper.GeometryHelper;
import net.fabricmc.indigo.renderer.mesh.EncodingFormat;
import net.fabricmc.indigo.renderer.mesh.MeshImpl;
import net.fabricmc.indigo.renderer.mesh.MutableQuadViewImpl;
@ -127,6 +128,8 @@ public class ItemRenderContext extends AbstractRenderContext implements RenderCo
@Override
public Maker emit() {
lightFace = GeometryHelper.lightFace(this);
ColorHelper.applyDiffuseShading(this, false);
renderQuad();
clear();
return this;
@ -168,7 +171,7 @@ public class ItemRenderContext extends AbstractRenderContext implements RenderCo
private int quadColor() {
final int colorIndex = editorQuad.colorIndex();
int quadColor = color;
if (!enchantment && quadColor == -1 && colorIndex != 1) {
if (!enchantment && quadColor == -1 && colorIndex != -1) {
quadColor = colorMap.getColorMultiplier(itemStack, colorIndex);
quadColor |= -16777216;
}
@ -182,7 +185,7 @@ public class ItemRenderContext extends AbstractRenderContext implements RenderCo
c = ColorHelper.multiplyColor(quadColor, c);
q.spriteColor(i, 0, ColorHelper.swapRedBlueIfNeeded(c));
}
fabricBuffer.fabric_putVanillaData(quadData, EncodingFormat.VERTEX_START_OFFSET, true);
fabricBuffer.fabric_putQuad(q);
}
private void renderQuad() {

View file

@ -25,6 +25,7 @@ import net.fabricmc.fabric.api.renderer.v1.mesh.QuadEmitter;
import net.fabricmc.fabric.api.renderer.v1.model.ModelHelper;
import net.fabricmc.fabric.api.renderer.v1.render.RenderContext.QuadTransform;
import net.fabricmc.indigo.renderer.RenderMaterialImpl.Value;
import net.fabricmc.indigo.Indigo;
import net.fabricmc.indigo.renderer.IndigoRenderer;
import net.fabricmc.indigo.renderer.aocalc.AoCalculator;
import net.fabricmc.indigo.renderer.helper.GeometryHelper;
@ -58,6 +59,20 @@ public class TerrainFallbackConsumer extends AbstractQuadRenderer implements Con
private static Value MATERIAL_FLAT = (Value) IndigoRenderer.INSTANCE.materialFinder().disableAo(0, true).find();
private static Value MATERIAL_SHADED = (Value) IndigoRenderer.INSTANCE.materialFinder().find();
/**
* Controls 1x warning for vanilla quad vertex format when running in compatibility mode.
*/
private static boolean logCompatibilityWarning = true;
private static boolean isCompatible(int[] vertexData) {
final boolean result = vertexData.length == 28;
if(!result && logCompatibilityWarning) {
logCompatibilityWarning = false;
Indigo.LOGGER.warn("[Indigo] Encountered baked quad with non-standard vertex format. Some blocks will not be rendered");
}
return result;
}
private final int[] editorBuffer = new int[28];
private final ChunkRenderInfo chunkInfo;
@ -109,7 +124,13 @@ public class TerrainFallbackConsumer extends AbstractQuadRenderer implements Con
}
private void renderQuad(BakedQuad quad, Direction cullFace, Value defaultMaterial) {
System.arraycopy(quad.getVertexData(), 0, editorBuffer, 0, 28);
final int[] vertexData = quad.getVertexData();
if(Indigo.ENSURE_VERTEX_FORMAT_COMPATIBILITY && !isCompatible(vertexData)) {
return;
}
final MutableQuadViewImpl editorQuad = this.editorQuad;
System.arraycopy(vertexData, 0, editorBuffer, 0, 28);
editorQuad.cullFace(cullFace);
final Direction lightFace = quad.getFace();
editorQuad.lightFace(lightFace);
@ -129,13 +150,14 @@ public class TerrainFallbackConsumer extends AbstractQuadRenderer implements Con
tesselateSmooth(editorQuad, blockInfo.defaultLayerIndex, editorQuad.colorIndex());
} else {
// vanilla compatibility hack
// For flat lighting, if cull face is set always use neighbor light.
// Otherwise still need to ensure geometry is updated before offsets are applied
// For flat lighting, cull face drives everything and light face is ignored.
if(cullFace == null) {
editorQuad.invalidateShape();
// Can't rely on lazy computation in tesselateFlat() because needs to happen before offsets are applied
editorQuad.geometryFlags();
} else {
editorQuad.geometryFlags(GeometryHelper.AXIS_ALIGNED_FLAG | GeometryHelper.LIGHT_FACE_FLAG);
editorQuad.geometryFlags(GeometryHelper.LIGHT_FACE_FLAG);
editorQuad.lightFace(cullFace);
}
chunkInfo.applyOffsets(editorQuad);
tesselateFlat(editorQuad, blockInfo.defaultLayerIndex, editorQuad.colorIndex());

View file

@ -24,8 +24,6 @@ import net.fabricmc.fabric.api.renderer.v1.model.FabricBakedModel;
import net.fabricmc.fabric.api.renderer.v1.render.RenderContext;
import net.fabricmc.indigo.renderer.aocalc.AoCalculator;
import net.minecraft.block.BlockState;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.render.block.BlockRenderManager;
import net.minecraft.client.render.chunk.ChunkRenderTask;
import net.minecraft.client.render.chunk.ChunkRenderer;
import net.minecraft.client.render.chunk.ChunkRendererRegion;
@ -34,7 +32,6 @@ import net.minecraft.util.crash.CrashException;
import net.minecraft.util.crash.CrashReport;
import net.minecraft.util.crash.CrashReportSection;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.ExtendedBlockView;
/**
* Implementation of {@link RenderContext} used during terrain rendering.
@ -48,7 +45,6 @@ public class TerrainRenderContext extends AbstractRenderContext implements Rende
private final AoCalculator aoCalc = new AoCalculator(blockInfo, chunkInfo::cachedBrightness, chunkInfo::cachedAoLevel);
private final TerrainMeshConsumer meshConsumer = new TerrainMeshConsumer(blockInfo, chunkInfo, aoCalc, this::transform);
private final TerrainFallbackConsumer fallbackConsumer = new TerrainFallbackConsumer(blockInfo, chunkInfo, aoCalc, this::transform);
private final BlockRenderManager blockRenderManager = MinecraftClient.getInstance().getBlockRenderManager();
public void setBlockView(ChunkRendererRegion blockView) {
blockInfo.setBlockView(blockView);

View file

@ -4,6 +4,8 @@
"compatibilityLevel": "JAVA_8",
"plugin": "net.fabricmc.indigo.IndigoMixinConfigPlugin",
"mixins": [
],
"client": [
"BufferBuilderOffsetAccessor",
"MixinBlockModelRenderer",
"MixinBufferBuilder",

View file

@ -1,5 +1,5 @@
archivesBaseName = "fabric-rendering-v0"
version = getSubprojectVersion(project, "0.1.0")
version = getSubprojectVersion(project, "0.1.1")
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.client.render;
import net.fabricmc.fabric.api.event.Event;
import net.fabricmc.fabric.api.event.EventFactory;
/**
* Called when the world renderer reloads, usually as result of changing resource pack
* or video configuration, or when the player types F3+A in the debug screen.
* Afterwards all render chunks will be reset and reloaded.<p>
*
* Render chunks and other render-related object instances will be made null
* or invalid after this event so do not use it to capture dependent state.
* Instead, use it to invalidate state and reinitialize lazily.
*/
public interface InvalidateRenderStateCallback {
public static final Event<InvalidateRenderStateCallback> EVENT = EventFactory.createArrayBacked(InvalidateRenderStateCallback.class,
(listeners) -> () -> {
for (InvalidateRenderStateCallback event : listeners) {
event.onInvalidate();
}
}
);
void onInvalidate();
}

View file

@ -0,0 +1,33 @@
/*
* 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.client.render;
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.CallbackInfo;
import net.fabricmc.fabric.api.client.render.InvalidateRenderStateCallback;
import net.minecraft.client.render.WorldRenderer;
@Mixin(WorldRenderer.class)
public abstract class MixinWorldRenderer {
@Inject(method = "reload", at = @At("HEAD"))
private void onReload(CallbackInfo ci) {
InvalidateRenderStateCallback.EVENT.invoker().onInvalidate();
}
}

View file

@ -6,7 +6,8 @@
"MixinBlockColorMap",
"MixinBlockEntityRenderManager",
"MixinEntityRenderManager",
"MixinItemColorMap"
"MixinItemColorMap",
"MixinWorldRenderer"
],
"injectors": {
"defaultRequire": 1

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