Merge branch 'master' into snapshot/1.14.3

This commit is contained in:
Adrian Siekierka 2019-06-13 10:03:02 +02:00
commit 10d8dc0969
54 changed files with 1472 additions and 203 deletions
fabric-content-registries-v0/src/main/java/net/fabricmc/fabric
fabric-loot-tables-v1
fabric-registry-sync-v0
fabric-renderer-indigo
fabric-rendering-v0
build.gradle
src/main
java/net/fabricmc/fabric
resources
fabric-tag-extensions-v0
fabric-testmods/java/net/fabricmc/fabric/loot
settings.gradle

View file

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

View file

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

View file

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

View file

@ -0,0 +1,39 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.api.loot.v1;
import net.minecraft.world.loot.LootPool;
import net.minecraft.world.loot.LootTableRange;
import net.minecraft.world.loot.condition.LootCondition;
import net.minecraft.world.loot.entry.LootEntry;
import net.minecraft.world.loot.function.LootFunction;
import java.util.List;
/**
* An interface implemented by all {@code net.minecraft.world.loot.LootPool} instances when
* Fabric API is present. Contains accessors for various fields.
*/
public interface FabricLootPool {
default LootPool asVanilla() {
return (LootPool) this;
}
List<LootEntry> getEntries();
List<LootCondition> getConditions();
List<LootFunction> getFunctions();
LootTableRange getRollsRange();
}

View file

@ -0,0 +1,110 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.api.loot.v1;
import net.fabricmc.fabric.mixin.loot.LootPoolBuilderHooks;
import net.minecraft.world.loot.LootPool;
import net.minecraft.world.loot.LootTableRange;
import net.minecraft.world.loot.condition.LootCondition;
import net.minecraft.world.loot.entry.LootEntry;
import net.minecraft.world.loot.function.LootFunction;
public class FabricLootPoolBuilder extends LootPool.Builder {
private final LootPoolBuilderHooks extended = (LootPoolBuilderHooks) this;
private FabricLootPoolBuilder() {}
private FabricLootPoolBuilder(LootPool pool) {
copyFrom(pool, true);
}
@Override
public FabricLootPoolBuilder withRolls(LootTableRange range) {
super.withRolls(range);
return this;
}
@Override
public FabricLootPoolBuilder withEntry(LootEntry.Builder<?> entry) {
super.withEntry(entry);
return this;
}
@Override
public FabricLootPoolBuilder withCondition(LootCondition.Builder condition) {
super.method_356(condition);
return this;
}
@Override
public FabricLootPoolBuilder withFunction(LootFunction.Builder function) {
super.method_353(function);
return this;
}
public FabricLootPoolBuilder withEntry(LootEntry entry) {
extended.getEntries().add(entry);
return this;
}
public FabricLootPoolBuilder withCondition(LootCondition condition) {
extended.getConditions().add(condition);
return this;
}
public FabricLootPoolBuilder withFunction(LootFunction function) {
extended.getFunctions().add(function);
return this;
}
/**
* Copies the entries, conditions and functions of the {@code pool} to this
* builder.
*
* This is equal to {@code copyFrom(pool, false)}.
*/
public FabricLootPoolBuilder copyFrom(LootPool pool) {
return copyFrom(pool, false);
}
/**
* Copies the entries, conditions and functions of the {@code pool} to this
* builder.
*
* If {@code copyRolls} is true, the {@link FabricLootPool#getRollsRange rolls} of the pool are also copied.
*/
public FabricLootPoolBuilder copyFrom(LootPool pool, boolean copyRolls) {
FabricLootPool extendedPool = (FabricLootPool) pool;
extended.getConditions().addAll(extendedPool.getConditions());
extended.getFunctions().addAll(extendedPool.getFunctions());
extended.getEntries().addAll(extendedPool.getEntries());
if (copyRolls) {
withRolls(extendedPool.getRollsRange());
}
return this;
}
public static FabricLootPoolBuilder builder() {
return new FabricLootPoolBuilder();
}
public static FabricLootPoolBuilder of(LootPool pool) {
return new FabricLootPoolBuilder(pool);
}
}

View file

@ -0,0 +1,37 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.api.loot.v1;
import net.minecraft.world.loot.LootPool;
import net.minecraft.world.loot.LootSupplier;
import net.minecraft.world.loot.context.LootContextType;
import net.minecraft.world.loot.function.LootFunction;
import java.util.List;
/**
* An interface implemented by all {@code net.minecraft.world.loot.LootSupplier} instances when
* Fabric API is present. Contains accessors for various fields.
*/
public interface FabricLootSupplier {
default LootSupplier asVanilla() {
return (LootSupplier) this;
}
List<LootPool> getPools();
List<LootFunction> getFunctions();
LootContextType getType();
}

View file

@ -0,0 +1,105 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.api.loot.v1;
import net.fabricmc.fabric.mixin.loot.LootSupplierBuilderHooks;
import net.minecraft.world.loot.LootPool;
import net.minecraft.world.loot.LootSupplier;
import net.minecraft.world.loot.context.LootContextType;
import net.minecraft.world.loot.function.LootFunction;
import java.util.Collection;
public class FabricLootSupplierBuilder extends LootSupplier.Builder {
private final LootSupplierBuilderHooks extended = (LootSupplierBuilderHooks) this;
protected FabricLootSupplierBuilder() {}
private FabricLootSupplierBuilder(LootSupplier supplier) {
copyFrom(supplier, true);
}
@Override
public FabricLootSupplierBuilder withPool(LootPool.Builder pool) {
super.withPool(pool);
return this;
}
@Override
public FabricLootSupplierBuilder withType(LootContextType type) {
super.withType(type);
return this;
}
@Override
public FabricLootSupplierBuilder withFunction(LootFunction.Builder function) {
super.method_335(function);
return this;
}
public FabricLootSupplierBuilder withPool(LootPool pool) {
extended.getPools().add(pool);
return this;
}
public FabricLootSupplierBuilder withFunction(LootFunction function) {
extended.getFunctions().add(function);
return this;
}
public FabricLootSupplierBuilder withPools(Collection<LootPool> pools) {
pools.forEach(this::withPool);
return this;
}
public FabricLootSupplierBuilder withFunctions(Collection<LootFunction> functions) {
functions.forEach(this::withFunction);
return this;
}
/**
* Copies the pools and functions of the {@code supplier} to this builder.
* This is equal to {@code copyFrom(supplier, false)}.
*/
public FabricLootSupplierBuilder copyFrom(LootSupplier supplier) {
return copyFrom(supplier, false);
}
/**
* Copies the pools and functions of the {@code supplier} to this builder.
* If {@code copyType} is true, the {@link FabricLootSupplier#getType type} of the supplier is also copied.
*/
public FabricLootSupplierBuilder copyFrom(LootSupplier supplier, boolean copyType) {
FabricLootSupplier extendedSupplier = (FabricLootSupplier) supplier;
extended.getPools().addAll(extendedSupplier.getPools());
extended.getFunctions().addAll(extendedSupplier.getFunctions());
if (copyType) {
withType(extendedSupplier.getType());
}
return this;
}
public static FabricLootSupplierBuilder builder() {
return new FabricLootSupplierBuilder();
}
public static FabricLootSupplierBuilder of(LootSupplier supplier) {
return new FabricLootSupplierBuilder(supplier);
}
}

View file

@ -0,0 +1,37 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.api.loot.v1;
import net.fabricmc.fabric.impl.loot.LootEntryTypeRegistryImpl;
import net.minecraft.world.loot.entry.LootEntry;
/**
* Fabric's extensions to {@code net.minecraft.world.loot.entry.LootEntries} for registering
* custom loot entry types.
*
* @see #register
*/
public interface LootEntryTypeRegistry {
final LootEntryTypeRegistry INSTANCE = LootEntryTypeRegistryImpl.INSTANCE;
/**
* Registers a loot entry type by its serializer.
*
* @param serializer the loot entry serializer
*/
void register(LootEntry.Serializer<?> serializer);
}

View file

@ -0,0 +1,66 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.api.loot.v1;
import com.google.gson.Gson;
import net.minecraft.resource.Resource;
import net.minecraft.resource.ResourceManager;
import net.minecraft.util.Identifier;
import net.minecraft.util.JsonHelper;
import net.minecraft.util.Lazy;
import net.minecraft.world.loot.*;
import net.minecraft.world.loot.condition.LootCondition;
import net.minecraft.world.loot.entry.LootEntry;
import net.minecraft.world.loot.function.LootFunction;
import org.apache.commons.io.IOUtils;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.lang.reflect.Field;
import java.nio.charset.StandardCharsets;
import java.util.Optional;
import java.util.stream.Stream;
public final class LootJsonParser {
/* Reading this from LootManager to access all serializers from vanilla. */
private static final Lazy<Gson> GSON = new Lazy<>(() -> {
try {
Field gsonField = Stream.of(LootManager.class.getDeclaredFields())
.filter(field -> field.getType() == Gson.class)
.findFirst()
.orElseThrow(() -> new RuntimeException("Gson not found in LootManager!"));
gsonField.setAccessible(true);
return (Gson) gsonField.get(null);
} catch (Exception e) {
throw new RuntimeException("Exception while getting Gson instance from LootManager", e);
}
});
private LootJsonParser() {
}
public static <T> T read(Reader json, Class<T> c) {
return JsonHelper.deserialize(GSON.get(), json, c);
}
public static <T> T read(String json, Class<T> c) {
return JsonHelper.deserialize(GSON.get(), json, c);
}
}

View file

@ -0,0 +1,48 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.api.loot.v1.event;
import net.fabricmc.fabric.api.event.Event;
import net.fabricmc.fabric.api.event.EventFactory;
import net.fabricmc.fabric.api.loot.v1.FabricLootSupplierBuilder;
import net.minecraft.resource.ResourceManager;
import net.minecraft.util.Identifier;
import net.minecraft.world.loot.LootManager;
import net.minecraft.world.loot.LootSupplier;
/**
* An event handler that is called when loot tables are loaded.
* Use {@link #EVENT} to register instances.
*/
@FunctionalInterface
public interface LootTableLoadingCallback {
@FunctionalInterface
interface LootTableSetter {
void set(LootSupplier supplier);
}
final Event<LootTableLoadingCallback> EVENT = EventFactory.createArrayBacked(
LootTableLoadingCallback.class,
(listeners) -> (resourceManager, manager, id, supplier, setter) -> {
for (LootTableLoadingCallback callback : listeners) {
callback.onLootTableLoading(resourceManager, manager, id, supplier, setter);
}
}
);
void onLootTableLoading(ResourceManager resourceManager, LootManager manager, Identifier id, FabricLootSupplierBuilder supplier, LootTableSetter setter);
}

View file

@ -0,0 +1,59 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.impl.loot;
import net.minecraft.world.loot.entry.LootEntries;
import net.minecraft.world.loot.entry.LootEntry;
import java.lang.reflect.Method;
public final class LootEntryTypeRegistryImpl implements net.fabricmc.fabric.api.loot.v1.LootEntryTypeRegistry {
public static final LootEntryTypeRegistryImpl INSTANCE = new LootEntryTypeRegistryImpl();
private static final Method REGISTER_METHOD;
static {
Method target = null;
for (Method m : LootEntries.class.getDeclaredMethods()) {
if (m.getParameterCount() == 1 && m.getParameterTypes()[0] == LootEntry.Serializer.class) {
if (target != null) {
throw new RuntimeException("More than one register-like method found in LootEntries!");
} else {
target = m;
}
}
}
if (target == null) {
throw new RuntimeException("Could not find register-like method in LootEntries!");
} else {
REGISTER_METHOD = target;
REGISTER_METHOD.setAccessible(true);
}
}
private LootEntryTypeRegistryImpl() {
}
@Override
public void register(LootEntry.Serializer<?> serializer) {
try {
REGISTER_METHOD.invoke(null, serializer);
} catch (Throwable t) {
throw new RuntimeException(t);
}
}
}

View file

@ -0,0 +1,36 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.mixin.loot;
import net.minecraft.world.loot.LootPool;
import net.minecraft.world.loot.condition.LootCondition;
import net.minecraft.world.loot.entry.LootEntry;
import net.minecraft.world.loot.function.LootFunction;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;
import java.util.List;
@Mixin(LootPool.Builder.class)
public interface LootPoolBuilderHooks {
@Accessor
List<LootEntry> getEntries();
@Accessor
List<LootCondition> getConditions();
@Accessor
List<LootFunction> getFunctions();
}

View file

@ -0,0 +1,33 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.mixin.loot;
import net.minecraft.world.loot.LootPool;
import net.minecraft.world.loot.LootSupplier;
import net.minecraft.world.loot.function.LootFunction;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.gen.Accessor;
import java.util.List;
@Mixin(LootSupplier.Builder.class)
public interface LootSupplierBuilderHooks {
@Accessor
List<LootPool> getPools();
@Accessor
List<LootFunction> getFunctions();
}

View file

@ -0,0 +1,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.loot;
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.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 @Final private Map<Identifier, LootSupplier> suppliers;
@Inject(method = "apply", at = @At("RETURN"))
private void apply(ResourceManager manager, 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());
});
for (Identifier id : newSuppliers.keySet()) {
suppliers.put(id, newSuppliers.get(id));
}
}
}

View file

@ -0,0 +1,59 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.mixin.loot;
import net.fabricmc.fabric.api.loot.v1.FabricLootPool;
import net.minecraft.world.loot.LootPool;
import net.minecraft.world.loot.LootTableRange;
import net.minecraft.world.loot.condition.LootCondition;
import net.minecraft.world.loot.entry.LootEntry;
import net.minecraft.world.loot.function.LootFunction;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.gen.Accessor;
import java.util.Arrays;
import java.util.List;
@Mixin(LootPool.class)
public abstract class MixinLootPool implements FabricLootPool {
@Shadow @Final private LootEntry[] entries;
@Shadow @Final private LootCondition[] conditions;
@Shadow @Final private LootFunction[] functions;
@Override
public List<LootEntry> getEntries() {
return Arrays.asList(entries);
}
@Override
public List<LootCondition> getConditions() {
return Arrays.asList(conditions);
}
@Override
public List<LootFunction> getFunctions() {
return Arrays.asList(functions);
}
@Accessor
@Override
public abstract LootTableRange getRollsRange();
}

View file

@ -0,0 +1,50 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.mixin.loot;
import net.fabricmc.fabric.api.loot.v1.FabricLootSupplier;
import net.minecraft.world.loot.LootPool;
import net.minecraft.world.loot.LootSupplier;
import net.minecraft.world.loot.context.LootContextType;
import net.minecraft.world.loot.function.LootFunction;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.gen.Accessor;
import java.util.Arrays;
import java.util.List;
@Mixin(LootSupplier.class)
public abstract class MixinLootSupplier implements FabricLootSupplier {
@Shadow @Final private LootPool[] pools;
@Shadow @Final private LootFunction[] functions;
@Override
public List<LootPool> getPools() {
return Arrays.asList(pools);
}
@Override
public List<LootFunction> getFunctions() {
return Arrays.asList(functions);
}
@Accessor
@Override
public abstract LootContextType getType();
}

View file

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

View file

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

View file

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

View file

@ -31,7 +31,7 @@ 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(

View file

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

View file

@ -25,10 +25,12 @@ 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, Int2IntMap rawIdChangeMap) {
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()) {
@ -44,7 +46,7 @@ public class RemapStateImpl<T> implements RegistryIdRemapCallback.RemapState<T>
@Override
public Identifier getIdFromOld(int oldRawId) {
return newIdMap.get(rawIdChangeMap.getOrDefault(oldRawId, -1));
return oldIdMap.get(oldRawId);
}
@Override

View file

@ -28,15 +28,17 @@ import java.util.HashMap;
import java.util.Map;
public class IdListTracker<V, OV> implements RegistryEntryAddedCallback<V>, RegistryIdRemapCallback<V>, RegistryEntryRemovedCallback<V> {
private final String name;
private final IdList<OV> mappers;
private Map<Identifier, OV> removedMapperCache = new HashMap<>();
private IdListTracker(IdList<OV> mappers) {
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<>(mappers);
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);

View file

@ -16,6 +16,8 @@
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 it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import net.fabricmc.fabric.api.event.registry.RegistryEntryAddedCallback;
@ -23,20 +25,27 @@ 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 RegistryEntryAddedCallback<V>, RegistryIdRemapCallback<V>, RegistryEntryRemovedCallback<V> {
private static final Logger LOGGER = LogManager.getLogger();
private final String name;
private final Int2ObjectMap<OV> mappers;
private Map<Identifier, OV> removedMapperCache = new HashMap<>();
private Int2ObjectMapTracker(Int2ObjectMap<OV> mappers) {
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<>(mappers);
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);
@ -52,22 +61,38 @@ public class Int2ObjectMapTracker<V, OV> implements RegistryEntryAddedCallback<V
@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 = state.getRawIdChangeMap().getOrDefault(i, i);
if (mappers.containsKey(newI)) {
throw new RuntimeException("Int2ObjectMap contained two equal IDs " + newI + " (" + state.getIdFromOld(i) + "/" + i + " -> " + state.getIdFromNew(newI) + "/" + newI + ")!");
}
int newI = remapMap.getOrDefault(i, Integer.MIN_VALUE);
if (newI >= 0) {
if (mappers.containsKey(newI)) {
if (errors == null) {
errors = new ArrayList<>();
}
mappers.put(newI, oldMappers.get(i));
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));
}
}
if (errors != null) {
throw new RuntimeException("Errors while remapping Int2ObjectMap " + name + " found:\n" + Joiner.on('\n').join(errors));
}
}
@Override
public void onEntryRemoved(int rawId, Identifier id, V object) {
if (mappers.containsKey(rawId)) {
removedMapperCache.put(id, mappers.remove(rawId));
OV mapper = mappers.remove(rawId);
if (mapper != null) {
removedMapperCache.put(id, mapper);
}
}
}

View file

@ -57,7 +57,7 @@ public final class StateIdTracker<T, S> implements RegistryIdRemapCallback<T>, R
stateGetter.apply(object).forEach(stateList::add);
currentHighestId = rawId;
} else {
logger.debug("[fabric-registry-sync] Non-sequential RegistryEntryAddedCallback for state ID tracker (at " + id + "), forcing state map recalculation...");
logger.debug("[fabric-registry-sync] Non-sequential RegistryEntryAddedCallback for " + object.getClass().getSimpleName() + " ID tracker (at " + id + "), forcing state map recalculation...");
recalcStateMap();
}
}

View file

@ -18,8 +18,7 @@ package net.fabricmc.fabric.mixin.registry;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import it.unimi.dsi.fastutil.ints.Int2IntMap;
import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
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;
@ -116,12 +115,21 @@ public abstract class MixinIdRegistry<T> implements RemappableRegistry, Listenab
@SuppressWarnings({"unchecked", "ConstantConditions"})
@Inject(method = "set", at = @At("HEAD"))
public void setPre(int id, Identifier identifier, Object object, CallbackInfoReturnable info) {
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);
int oldId = indexedEntries.getId(oldObject);
if (oldObject != object || oldId != id) {
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 {
@ -148,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);
@ -164,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)");
}
}
@ -196,53 +208,68 @@ 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 saved/remote 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);
Object object = registry.get(id);
// Emit RemoveObject events for removed objects.
//noinspection unchecked
fabric_getRemoveObjectEvent().invoker().onEntryRemoved(registry.getRawId(object), id, (T) object);
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;
}
Int2IntMap idMap = new Int2IntOpenHashMap();
for (Object o : indexedEntries) {
Identifier id = registry.getId(o);
idMap.put(registry.getRawId(o), remoteIndexedEntries.getInt(id));
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.
@ -276,7 +303,7 @@ public abstract class MixinIdRegistry<T> implements RemappableRegistry, Listenab
}
//noinspection unchecked
fabric_getRemapEvent().invoker().onRemap(new RemapStateImpl(registry, idMap));
fabric_getRemapEvent().invoker().onRemap(new RemapStateImpl(registry, oldIdMap, idMap));
}
@Override

View file

@ -30,12 +30,10 @@ 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 {
@ -49,6 +47,7 @@ public class MixinWorldSaveHandler {
@Unique
private CompoundTag fabric_lastSavedIdMap = null;
@Unique
private boolean fabric_readIdMapFile(File file) throws IOException, RemapException {
if (file.exists()) {
FileInputStream fileInputStream = new FileInputStream(file);
@ -63,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);
@ -88,29 +136,7 @@ public class MixinWorldSaveHandler {
}
}
CompoundTag newIdMap = RegistrySyncManager.toTag(false);
if (!newIdMap.equals(fabric_lastSavedIdMap)) {
for (int i = FABRIC_ID_REGISTRY_BACKUPS - 1; i >= 0; i--) {
File file = getWorldIdMapFile(i);
if (file.exists()) {
if (i == FABRIC_ID_REGISTRY_BACKUPS - 1) {
file.delete();
} else {
File target = getWorldIdMapFile(i + 1);
file.renameTo(target);
}
}
}
try {
FileOutputStream fileOutputStream = new FileOutputStream(getWorldIdMapFile(0));
NbtIo.writeCompressed(newIdMap, fileOutputStream);
fileOutputStream.close();
} catch (IOException e) {
FABRIC_LOGGER.warn("Failed to save registry file!", e);
}
fabric_lastSavedIdMap = newIdMap;
}
// If not returned (not present), try saving the registry data
fabric_saveRegistryData();
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -35,6 +35,11 @@ import java.util.Properties;
public class Indigo implements ClientModInitializer {
public static final boolean ALWAYS_TESSELATE_INDIGO;
public static final AoConfig AMBIENT_OCCLUSION_MODE;
/** 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;
private static final Logger LOGGER = LogManager.getLogger();
private static boolean asBoolean(String property, boolean defValue) {
@ -98,7 +103,10 @@ 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);
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);
try (FileOutputStream stream = new FileOutputStream(configFile)) {
properties.store(stream, "Indigo properties file");

View file

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

View file

@ -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,12 +110,8 @@ 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;
switch(config) {
@ -127,7 +121,7 @@ public class AoCalculator {
case EMULATE:
calcFastVanilla(quad);
shouldMatch = DEBUG && isVanilla;
shouldMatch = Indigo.DEBUG_COMPARE_LIGHTING && isVanilla;
break;
case HYBRID:
@ -164,7 +158,7 @@ public class AoCalculator {
}
}
}
private void calcVanilla(MutableQuadViewImpl quad) {
vanillaCalc.compute(blockInfo, quad, ao, light);
}
@ -173,28 +167,15 @@ 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 */
@ -202,11 +183,11 @@ public class AoCalculator {
switch(quad.geometryFlags()) {
case AXIS_ALIGNED_FLAG | CUBIC_FLAG | LIGHT_FACE_FLAG:
vanillaFullFace(quad, true);
return DEBUG;
return Indigo.DEBUG_COMPARE_LIGHTING;
case AXIS_ALIGNED_FLAG | LIGHT_FACE_FLAG:
vanillaPartialFace(quad, true);
return DEBUG;
return Indigo.DEBUG_COMPARE_LIGHTING;
case AXIS_ALIGNED_FLAG | CUBIC_FLAG:
blendedFullFace(quad);
@ -222,12 +203,12 @@ public class AoCalculator {
}
}
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 +220,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 +231,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 +246,12 @@ public class AoCalculator {
}
}
private void blendedFullFace(MutableQuadViewImpl quad) {
private void blendedFullFace(QuadViewImpl 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;
@ -383,16 +364,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 +450,7 @@ public class AoCalculator {
* value from all four samples.
*/
private static int meanBrightness(int a, int b, int c, int d) {
if(fixSmoothLighting) {
if(Indigo.FIX_SMOOTH_LIGHTING_OFFSET) {
return a == 0 || b == 0 || c == 0 || d == 0 ? meanEdgeBrightness(a, b, c, d) : meanInnerBrightness(a, b, c, d);
} else {
return vanillaMeanBrightness(a, b, c, d);

View file

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

View file

@ -0,0 +1,29 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.indigo.renderer.aocalc;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.fabricmc.indigo.Indigo;
@Environment(EnvType.CLIENT)
@FunctionalInterface
interface AoVertexClampFunction {
float clamp(float x);
AoVertexClampFunction CLAMP_FUNC = Indigo.FIX_EXTERIOR_VERTEX_LIGHTING ? x -> x < 0f ? 0f : (x > 1f ? 1f : x) : x -> x;
}

View file

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

View file

@ -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;
@ -140,7 +141,7 @@ public abstract class AbstractQuadRenderer {
*/
int flatBrightness(MutableQuadViewImpl quad, BlockState blockState, BlockPos pos) {
mpos.set(pos);
if((quad.geometryFlags() & LIGHT_FACE_FLAG) != 0) {
if((quad.geometryFlags() & LIGHT_FACE_FLAG) != 0 || Block.isShapeFullCube(blockState.getCollisionShape(blockInfo.blockView, pos))) {
mpos.setOffset(quad.lightFace());
}
return brightnessFunc.applyAsInt(blockState, mpos);

View file

@ -132,7 +132,7 @@ public class TerrainFallbackConsumer extends AbstractQuadRenderer implements Con
// vanilla compatibility hack
// For flat lighting, cull face drives everything and light face is ignored.
if(cullFace == null) {
editorQuad.geometryFlags(0);
editorQuad.invalidateShape();
} else {
editorQuad.geometryFlags(GeometryHelper.LIGHT_FACE_FLAG);
editorQuad.lightFace(cullFace);

View file

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

View file

@ -0,0 +1,41 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.api.client.render;
import net.fabricmc.fabric.api.event.Event;
import net.fabricmc.fabric.api.event.EventFactory;
/**
* Called when the world renderer reloads, usually as result of changing resource pack
* or video configuration, or when the player types F3+A in the debug screen.
* Afterwards all render chunks will be reset and reloaded.<p>
*
* Render chunks and other render-related object instances will be made null
* or invalid after this event so do not use it to capture dependent state.
* Instead, use it to invalidate state and reinitialize lazily.
*/
public interface InvalidateRenderStateCallback {
public static final Event<InvalidateRenderStateCallback> EVENT = EventFactory.createArrayBacked(InvalidateRenderStateCallback.class,
(listeners) -> () -> {
for (InvalidateRenderStateCallback event : listeners) {
event.onInvalidate();
}
}
);
void onInvalidate();
}

View file

@ -0,0 +1,33 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.mixin.client.render;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import net.fabricmc.fabric.api.client.render.InvalidateRenderStateCallback;
import net.minecraft.client.render.WorldRenderer;
@Mixin(WorldRenderer.class)
public abstract class MixinWorldRenderer {
@Inject(method = "reload", at = @At("HEAD"))
private void onReload(CallbackInfo ci) {
InvalidateRenderStateCallback.EVENT.invoker().onInvalidate();
}
}

View file

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

View file

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

View file

@ -0,0 +1,30 @@
/*
* 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.tag;
/**
* Interface implemented by {@link net.minecraft.tag.Tag} instances when
* Fabric API is present.
*
* @param <T>
*/
public interface FabricTag<T> {
/**
* @return True if the given tag has been "replaced" by a datapack at least once.
*/
boolean hasBeenReplaced();
}

View file

@ -0,0 +1,30 @@
/*
* 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.tag;
/**
* Interface implemented by {@link net.minecraft.tag.Tag.Builder} instances when
* Fabric API is present.
*
* @param <T>
*/
public interface FabricTagBuilder<T> {
/**
* Clear the contained entries and mark the tag as replaced.
*/
void clearTagEntries();
}

View file

@ -0,0 +1,21 @@
/*
* 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.tag;
public interface FabricTagHooks {
void fabric_setExtraData(int clearCount);
}

View file

@ -0,0 +1,39 @@
/*
* Copyright (c) 2016, 2017, 2018, 2019 FabricMC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package net.fabricmc.fabric.mixin.tag;
import net.fabricmc.fabric.api.tag.FabricTag;
import net.fabricmc.fabric.impl.tag.FabricTagHooks;
import net.minecraft.tag.Tag;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
@Mixin(Tag.class)
public class MixinTag<T> implements FabricTag<T>, FabricTagHooks {
@Unique
private int fabric_clearCount;
@Override
public boolean hasBeenReplaced() {
return fabric_clearCount > 0;
}
@Override
public void fabric_setExtraData(int clearCount) {
this.fabric_clearCount = clearCount;
}
}

View file

@ -0,0 +1,60 @@
/*
* 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.tag;
import com.google.common.collect.Sets;
import com.google.gson.JsonObject;
import net.fabricmc.fabric.api.tag.FabricTagBuilder;
import net.fabricmc.fabric.impl.tag.FabricTagHooks;
import net.minecraft.tag.Tag;
import net.minecraft.util.Identifier;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import java.util.Set;
import java.util.function.Function;
import java.util.function.Predicate;
@Mixin(Tag.Builder.class)
public class MixinTagBuilder<T> implements FabricTagBuilder<T> {
@Shadow
private Set<Tag.Entry<T>> entries;
@Unique
private int fabric_clearCount;
@Inject(at = @At("RETURN"), method = "build")
public void onBuildFinished(Identifier id, CallbackInfoReturnable<Tag<T>> info) {
((FabricTagHooks) info.getReturnValue()).fabric_setExtraData(fabric_clearCount);
}
@Inject(at = @At(value = "INVOKE", target = "Ljava/util/Set;clear()V"), method = "fromJson")
public void onFromJsonClear(Function<Identifier, T> function_1, JsonObject jsonObject_1, CallbackInfoReturnable<Tag.Builder<T>> info) {
fabric_clearCount++;
}
@Override
public void clearTagEntries() {
entries.clear();
fabric_clearCount++;
}
}

View file

@ -0,0 +1,12 @@
{
"required": true,
"package": "net.fabricmc.fabric.mixin.tag",
"compatibilityLevel": "JAVA_8",
"mixins": [
"MixinTag",
"MixinTagBuilder"
],
"injectors": {
"defaultRequire": 1
}
}

View file

@ -7,5 +7,8 @@
"fabricloader": ">=0.4.0",
"fabric-api-base": "*",
"fabric-resource-loader-v0": "*"
}
},
"mixins": [
"fabric-tag-extensions-v0.mixins.json"
]
}

View file

@ -0,0 +1,50 @@
/*
* Copyright (c) 2016, 2017, 2018 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.loot;
import net.fabricmc.api.ModInitializer;
import net.fabricmc.fabric.api.event.loot.LootTableLoadingCallback;
import net.fabricmc.fabric.api.loot.FabricLootPoolBuilder;
import net.fabricmc.fabric.api.loot.LootUtilities;
import net.minecraft.item.Items;
import net.minecraft.world.loot.ConstantLootTableRange;
import net.minecraft.world.loot.LootPool;
import net.minecraft.world.loot.condition.SurvivesExplosionLootCondition;
import net.minecraft.world.loot.entry.ItemEntry;
import net.minecraft.world.loot.entry.LootEntry;
public class LootTableMod implements ModInitializer {
private static final String LOOT_ENTRY_JSON = "{\"type\":\"minecraft:item\",\"name\":\"minecraft:apple\"}";
@Override
public void onInitialize() {
LootTableLoadingCallback.EVENT.register((manager, id, supplier) -> {
if ("minecraft:blocks/dirt".equals(id.toString())) {
LootEntry entryFromString = LootUtilities.readEntryFromJson(LOOT_ENTRY_JSON);
LootPool pool = FabricLootPoolBuilder.builder()
.withEntry(ItemEntry.builder(Items.FEATHER))
.withEntry(entryFromString)
.withRolls(ConstantLootTableRange.create(1))
.withCondition(SurvivesExplosionLootCondition.method_871())
.build();
supplier.withPool(pool);
}
});
}
}

View file

@ -22,6 +22,7 @@ include 'fabric-events-interaction-v0'
include 'fabric-events-lifecycle-v0'
include 'fabric-item-groups-v0'
include 'fabric-keybindings-v0'
include 'fabric-loot-tables-v1'
include 'fabric-mining-levels-v0'
include 'fabric-models-v0'
include 'fabric-networking-v0'