forked from FabricMC/fabric
Merge branches 'pull/219', 'pull/300' and 'pull/301' into us/master
This commit is contained in:
commit
ab421b9c5c
112 changed files with 3489 additions and 662 deletions
|
@ -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"
|
||||
|
|
2
fabric-biomes-v1/build.gradle
Normal file
2
fabric-biomes-v1/build.gradle
Normal file
|
@ -0,0 +1,2 @@
|
|||
archivesBaseName = "fabric-biomes-v1"
|
||||
version = getSubprojectVersion(project, "0.1.0")
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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)));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
18
fabric-biomes-v1/src/main/resources/fabric.mod.json
Normal file
18
fabric-biomes-v1/src/main/resources/fabric.mod.json
Normal 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"
|
||||
]
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"fabric.gui.creativeTabPage": "Leht %d/%d"
|
||||
}
|
|
@ -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')
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
|
@ -4,7 +4,8 @@
|
|||
"compatibilityLevel": "JAVA_8",
|
||||
"client": [
|
||||
"MixinGameOptions",
|
||||
"MixinKeyBinding"
|
||||
"MixinKeyBinding",
|
||||
"KeyCodeAccessor"
|
||||
],
|
||||
"injectors": {
|
||||
"defaultRequire": 1
|
||||
|
|
6
fabric-loot-tables-v1/build.gradle
Normal file
6
fabric-loot-tables-v1/build.gradle
Normal 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')
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -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();
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
{
|
||||
"required": true,
|
||||
"package": "net.fabricmc.fabric.mixin.loot",
|
||||
"compatibilityLevel": "JAVA_8",
|
||||
"mixins": [
|
||||
"LootPoolBuilderHooks",
|
||||
"LootSupplierBuilderHooks",
|
||||
"MixinLootManager",
|
||||
"MixinLootPool",
|
||||
"MixinLootSupplier"
|
||||
],
|
||||
"injectors": {
|
||||
"defaultRequire": 1
|
||||
}
|
||||
}
|
13
fabric-loot-tables-v1/src/main/resources/fabric.mod.json
Normal file
13
fabric-loot-tables-v1/src/main/resources/fabric.mod.json
Normal 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"
|
||||
]
|
||||
}
|
|
@ -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]);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,5 +23,5 @@ import org.spongepowered.asm.mixin.gen.Accessor;
|
|||
@Mixin(MiningToolItem.class)
|
||||
public interface MiningToolItemAccessor {
|
||||
@Accessor
|
||||
float getBlockBreakingSpeed();
|
||||
float getMiningSpeed();
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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())
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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.");
|
||||
|
|
|
@ -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) {
|
||||
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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,
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -4,6 +4,8 @@
|
|||
"compatibilityLevel": "JAVA_8",
|
||||
"plugin": "net.fabricmc.indigo.IndigoMixinConfigPlugin",
|
||||
"mixins": [
|
||||
],
|
||||
"client": [
|
||||
"BufferBuilderOffsetAccessor",
|
||||
"MixinBlockModelRenderer",
|
||||
"MixinBufferBuilder",
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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();
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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
Loading…
Reference in a new issue