Skip to content

Commit

Permalink
More porting
Browse files Browse the repository at this point in the history
  • Loading branch information
AlphaMode committed Aug 16, 2024
1 parent 33a13b3 commit 553e595
Show file tree
Hide file tree
Showing 94 changed files with 2,521 additions and 840 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import io.github.fabricators_of_create.porting_lib.block.CustomExpBlock;
import net.minecraft.core.BlockPos;
import net.minecraft.util.RandomSource;
import net.minecraft.util.valueproviders.IntProvider;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.block.SculkCatalystBlock;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import io.github.fabricators_of_create.porting_lib.block.CustomExpBlock;
import net.minecraft.core.BlockPos;
import net.minecraft.util.RandomSource;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.block.SculkSensorBlock;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import io.github.fabricators_of_create.porting_lib.block.CustomExpBlock;
import net.minecraft.core.BlockPos;
import net.minecraft.util.RandomSource;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.level.block.SculkShriekerBlock;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
"common.ConnectionMixin",
"common.DataFixersMixin",
"common.DeadBushBlockMixin",
"common.DropExperienceBlockMixin",
"io.github.fabricators_of_create.porting_lib.blocks.mixin.DropExperienceBlockMixin",
"common.EnchantmentMenuMixin",
"common.EnchantmentTableBlockMixin",
"common.EntityCollisionContextMixin",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package io.github.fabricators_of_create.porting_lib.block;
package io.github.fabricators_of_create.porting_lib.blocks.extensions;

import net.minecraft.core.BlockPos;
import net.minecraft.world.entity.Entity;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
package io.github.fabricators_of_create.porting_lib.mixin.common;
package io.github.fabricators_of_create.porting_lib.blocks.mixin;

import io.github.fabricators_of_create.porting_lib.blocks.extensions.CustomExpBlock;
import net.minecraft.world.item.ItemStack;

import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;

import io.github.fabricators_of_create.porting_lib.block.CustomExpBlock;
import net.minecraft.core.BlockPos;
import net.minecraft.util.valueproviders.IntProvider;
import net.minecraft.world.level.block.DropExperienceBlock;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"defaultRequire": 1
},
"mixins": [
"BlockBehaviourMixin"
"BlockBehaviourMixin",
"DropExperienceBlockMixin"
]
}
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -0,0 +1,259 @@
package io.github.fabricators_of_create.porting_lib.conditions;

import com.mojang.datafixers.util.Pair;
import com.mojang.serialization.Codec;
import com.mojang.serialization.DataResult;
import com.mojang.serialization.Decoder;
import com.mojang.serialization.DynamicOps;
import com.mojang.serialization.Encoder;
import com.mojang.serialization.MapCodec;
import java.util.List;
import java.util.Optional;

import io.github.fabricators_of_create.porting_lib.core.util.PortingLibExtraCodecs;
import net.fabricmc.fabric.api.resource.conditions.v1.ResourceCondition;
import net.fabricmc.fabric.api.resource.conditions.v1.ResourceConditions;
import net.minecraft.core.HolderLookup;
import net.minecraft.resources.RegistryOps;
import net.minecraft.util.ExtraCodecs;

import org.jetbrains.annotations.Nullable;

/**
* Extension of {@link RegistryOps} that also encapsulates a {@link ICondition.IContext}.
* This allows getting the {@link ICondition.IContext} while decoding an entry from within a codec.
*/
public class ConditionalOps<T> extends RegistryOps<T> {
private final ICondition.IContext context;
private @Nullable HolderLookup.Provider registryLookup;

public ConditionalOps(RegistryOps<T> ops, ICondition.IContext context) {
super(ops, ops.lookupProvider);
this.context = context;
}

/**
* Returns a codec that can retrieve a {@link ICondition.IContext} from a registry ops,
* for example with {@code retrieveContext().decode(ops, ops.emptyMap())}.
*/
public static MapCodec<ICondition.IContext> retrieveContext() {
return ExtraCodecs.retrieveContext(ops -> {
if (!(ops instanceof ConditionalOps<?> conditionalOps))
return DataResult.success(ICondition.IContext.EMPTY);

return DataResult.success(conditionalOps.context);
});
}

/**
* Key used for the conditions inside an object.
*/
public static final String DEFAULT_CONDITIONS_KEY = ResourceConditions.CONDITIONS_KEY;
/**
* Key used to store the value associated with conditions,
* when the value is not represented as a map.
* For example, if we wanted to store the value 2 with some conditions, we could do:
*
* <pre>
* {
* "neoforge:conditions": [ ... ],
* "neoforge:value": 2
* }
* </pre>
*/
public static final String CONDITIONAL_VALUE_KEY = "neoforge:value";

/**
* @see #createConditionalCodec(Codec, String)
*/
public static <T> Codec<Optional<T>> createConditionalCodec(final Codec<T> ownerCodec) {
return createConditionalCodec(ownerCodec, DEFAULT_CONDITIONS_KEY);
}

/**
* Creates a conditional codec.
*
* <p>The conditional codec is generally not suitable for use as a dispatch target because it is never a {@link MapCodec.MapCodecCodec}.
*/
public static <T> Codec<Optional<T>> createConditionalCodec(final Codec<T> ownerCodec, String conditionalsKey) {
return createConditionalCodecWithConditions(ownerCodec, conditionalsKey).xmap(r -> r.map(WithConditions::carrier), r -> r.map(i -> new WithConditions<>(List.of(), i)));
}

/**
* Creates a codec that can decode a list of elements, and will check for conditions on each element.
*/
public static <T> Codec<List<T>> decodeListWithElementConditions(final Codec<T> ownerCodec) {
return Codec.of(
ownerCodec.listOf(),
PortingLibExtraCodecs.listWithOptionalElements(createConditionalCodec(ownerCodec)));
}

/**
* @see #createConditionalCodecWithConditions(Codec, String)
*/
public static <T> Codec<Optional<WithConditions<T>>> createConditionalCodecWithConditions(final Codec<T> ownerCodec) {
return createConditionalCodecWithConditions(ownerCodec, DEFAULT_CONDITIONS_KEY);
}

/**
* @see #createConditionalCodecWithConditions(HolderLookup.Provider, Codec, String)
*/
public static <T> Codec<Optional<WithConditions<T>>> createConditionalCodecWithConditions(@Nullable HolderLookup.Provider registryLookup, final Codec<T> ownerCodec) {
return createConditionalCodecWithConditions(registryLookup, ownerCodec, DEFAULT_CONDITIONS_KEY);
}

/**
* Creates a conditional codec.
*
* <p>The conditional codec is generally not suitable for use as a dispatch target because it is never a {@link MapCodec.MapCodecCodec}.
*/
public static <T> Codec<Optional<WithConditions<T>>> createConditionalCodecWithConditions(final Codec<T> ownerCodec, String conditionalsKey) {
return createConditionalCodecWithConditions(null, ownerCodec, conditionalsKey);
}

/**
* Creates a conditional codec.
*
* <p>The conditional codec is generally not suitable for use as a dispatch target because it is never a {@link MapCodec.MapCodecCodec}.
*/
public static <T> Codec<Optional<WithConditions<T>>> createConditionalCodecWithConditions(@Nullable HolderLookup.Provider registryLookup, final Codec<T> ownerCodec, String conditionalsKey) {
return Codec.of(
new ConditionalEncoder<>(conditionalsKey, ICondition.LIST_CODEC, ownerCodec),
new ConditionalDecoder<>(registryLookup, conditionalsKey, ICondition.LIST_CODEC, retrieveContext().codec(), ownerCodec));
}

private static final class ConditionalEncoder<A> implements Encoder<Optional<WithConditions<A>>> {
private final String conditionalsPropertyKey;
public final Codec<List<ResourceCondition>> conditionsCodec;
private final Encoder<A> innerCodec;

private ConditionalEncoder(String conditionalsPropertyKey, Codec<List<ResourceCondition>> conditionsCodec, Encoder<A> innerCodec) {
this.conditionalsPropertyKey = conditionalsPropertyKey;
this.conditionsCodec = conditionsCodec;
this.innerCodec = innerCodec;
}

@Override
public <T> DataResult<T> encode(Optional<WithConditions<A>> input, DynamicOps<T> ops, T prefix) {
if (ops.compressMaps()) {
// Compressing ops are not supported at the moment because they require special handling.
return DataResult.error(() -> "Cannot use ConditionalCodec with compressing DynamicOps");
}

if (input.isEmpty()) {
// Optional must be present for encoding.
return DataResult.error(() -> "Cannot encode empty Optional with a ConditionalEncoder. We don't know what to encode to!");
}

final WithConditions<A> withConditions = input.get();

if (withConditions.conditions().isEmpty()) {
// If there are no conditions, forward to the inner codec directly.
return innerCodec.encode(withConditions.carrier(), ops, prefix);
}

// By now we know we will produce a map-like object, so let's start building one.
var recordBuilder = ops.mapBuilder();
// Add conditions
recordBuilder.add(conditionalsPropertyKey, conditionsCodec.encodeStart(ops, withConditions.conditions()));

// Serialize the object
var encodedInner = innerCodec.encodeStart(ops, withConditions.carrier());

return encodedInner.flatMap(inner -> {
return ops.getMap(inner).map(innerMap -> {
// If the inner is a map...
if (innerMap.get(conditionalsPropertyKey) != null || innerMap.get(CONDITIONAL_VALUE_KEY) != null) {
// Conditional or value key cannot be used in the inner codec!
return DataResult.<T>error(() -> "Cannot wrap a value that already uses the condition or value key with a ConditionalCodec.");
}
// Copy all fields to the record builder
innerMap.entries().forEach(pair -> {
recordBuilder.add(pair.getFirst(), pair.getSecond());
});
return recordBuilder.build(prefix);
}).result().orElseGet(() -> {
// If the inner is not a map, write it to a value field
recordBuilder.add(CONDITIONAL_VALUE_KEY, inner);
return recordBuilder.build(prefix);
});
});
}

@Override
public String toString() {
return "Conditional[" + innerCodec + "]";
}
}

private static final class ConditionalDecoder<A> implements Decoder<Optional<WithConditions<A>>> {
private final String conditionalsPropertyKey;
public final Codec<List<ResourceCondition>> conditionsCodec;
private final Codec<ICondition.IContext> contextCodec;
private final Decoder<A> innerCodec;
private final @Nullable HolderLookup.Provider registryLookup;

private ConditionalDecoder(@Nullable HolderLookup.Provider registryLookup, String conditionalsPropertyKey, Codec<List<ResourceCondition>> conditionsCodec, Codec<ICondition.IContext> contextCodec, Decoder<A> innerCodec) {
this.registryLookup = registryLookup;
this.conditionalsPropertyKey = conditionalsPropertyKey;
this.conditionsCodec = conditionsCodec;
this.contextCodec = contextCodec;
this.innerCodec = innerCodec;
}

// Note: I am not too sure of what to return in the second element of the pair.
// If this turns out to be a problem, please change it but also document it and write some test cases.
@Override
public <T> DataResult<Pair<Optional<WithConditions<A>>, T>> decode(DynamicOps<T> ops, T input) {
if (ops.compressMaps()) {
// Compressing ops are not supported at the moment because they require special handling.
return DataResult.error(() -> "Cannot use ConditionalCodec with compressing DynamicOps");
}

return ops.getMap(input).map(inputMap -> {
final T conditionsDataCarrier = inputMap.get(conditionalsPropertyKey);
if (conditionsDataCarrier == null) {
// No conditions, forward to inner codec
return innerCodec.decode(ops, input).map(result -> result.mapFirst(carrier -> Optional.of(new WithConditions<>(carrier))));
}

return conditionsCodec.decode(ops, conditionsDataCarrier).flatMap(conditionsCarrier -> {
final List<ResourceCondition> conditions = conditionsCarrier.getFirst();
final DataResult<Pair<ICondition.IContext, T>> contextDataResult = contextCodec.decode(ops, ops.emptyMap());

return contextDataResult.flatMap(contextCarrier -> {
final ICondition.IContext context = contextCarrier.getFirst();

final boolean conditionsMatch = conditions.stream().allMatch(c -> c instanceof ICondition nc ? nc.test(registryLookup, context) : c.test(registryLookup));
if (!conditionsMatch)
return DataResult.success(Pair.of(Optional.empty(), input));

DataResult<Pair<A, T>> innerDecodeResult;

T valueDataCarrier = inputMap.get(CONDITIONAL_VALUE_KEY);
if (valueDataCarrier != null) {
// If there is a value field use its contents to deserialize.
innerDecodeResult = innerCodec.decode(ops, valueDataCarrier);
} else {
// Else copy the input into a new map without our custom key and decode from that.
T conditionalsKey = ops.createString(conditionalsPropertyKey);
var mapForDecoding = ops.createMap(inputMap
.entries()
.filter(pair -> !pair.getFirst().equals(conditionalsKey)));
innerDecodeResult = innerCodec.decode(ops, mapForDecoding);
}

// Variable is required because type inference can't handle this
DataResult<Pair<Optional<WithConditions<A>>, T>> ret = innerDecodeResult.map(
result -> result.mapFirst(
carrier -> Optional.of(new WithConditions<>(conditions, carrier))));
return ret;
});
});
}).result().orElseGet(() -> {
// Not a map, forward to inner codec
return innerCodec.decode(ops, input).map(result -> result.mapFirst(carrier -> Optional.of(new WithConditions<>(carrier))));
});
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package io.github.fabricators_of_create.porting_lib.conditions;

import net.fabricmc.fabric.api.resource.conditions.v1.ResourceCondition;
import net.minecraft.core.Holder;
import net.minecraft.core.HolderLookup;
import net.minecraft.core.Registry;
import net.minecraft.resources.ResourceKey;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.tags.TagKey;

import org.jetbrains.annotations.Nullable;

import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.Set;

public interface ICondition extends ResourceCondition {

@Override
default boolean test(@Nullable HolderLookup.Provider registryLookup) {
return test(registryLookup, IContext.TAGS_INVALID);
}

boolean test(@Nullable HolderLookup.Provider registryLookup, IContext context);


interface IContext {
IContext EMPTY = new IContext() {
@Override
public <T> Map<ResourceLocation, Collection<Holder<T>>> getAllTags(ResourceKey<? extends Registry<T>> registry) {
return Collections.emptyMap();
}
};

IContext TAGS_INVALID = new IContext() {
@Override
public <T> Map<ResourceLocation, Collection<Holder<T>>> getAllTags(ResourceKey<? extends Registry<T>> registry) {
throw new UnsupportedOperationException("Usage of tag-based conditions is not permitted in this context!");
}
};

/**
* Return the requested tag if available, or an empty tag otherwise.
*/
default <T> Collection<Holder<T>> getTag(TagKey<T> key) {
return getAllTags(key.registry()).getOrDefault(key.location(), Set.of());
}

/**
* Return all the loaded tags for the passed registry, or an empty map if none is available.
* Note that the map and the tags are unmodifiable.
*/
<T> Map<ResourceLocation, Collection<Holder<T>>> getAllTags(ResourceKey<? extends Registry<T>> registry);
}
}
Loading

0 comments on commit 553e595

Please sign in to comment.