Move state maps to resources

This commit is contained in:
Sollace 2022-08-29 00:47:39 +02:00
parent 3d704b7def
commit 1de9313610
15 changed files with 929 additions and 361 deletions

View file

@ -17,7 +17,6 @@ import com.minelittlepony.unicopia.ability.magic.spell.effect.SpellType;
import com.minelittlepony.unicopia.ability.magic.spell.trait.TraitLoader;
import com.minelittlepony.unicopia.advancement.UCriteria;
import com.minelittlepony.unicopia.block.UBlocks;
import com.minelittlepony.unicopia.block.state.StateMaps;
import com.minelittlepony.unicopia.command.Commands;
import com.minelittlepony.unicopia.container.UScreenHandlers;
import com.minelittlepony.unicopia.entity.UEntities;
@ -70,7 +69,6 @@ public class Unicopia implements ModInitializer {
Race.bootstrap();
SpellType.bootstrap();
Abilities.bootstrap();
StateMaps.bootstrap();
UScreenHandlers.bootstrap();
}

View file

@ -1,15 +1,24 @@
package com.minelittlepony.unicopia.block.state;
import java.util.Optional;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.block.enums.DoubleBlockHalf;
import net.minecraft.state.property.Properties;
import net.minecraft.util.Identifier;
import net.minecraft.util.math.BlockPos;
import net.minecraft.world.World;
public interface BlockStateConverter {
static ReversableBlockStateConverter of(Identifier id) {
return new StateMapLoader.Indirect<ReversableBlockStateConverter>(id, Optional.empty());
}
/**
* Checks if this collection contains a mapping capable of converting the given state.
*
@ -49,16 +58,17 @@ public interface BlockStateConverter {
}
if (!newState.contains(Properties.DOUBLE_BLOCK_HALF)) {
world.setBlockState(pos, newState, 16 | 2);
world.setBlockState(pos, newState, Block.FORCE_STATE | Block.NOTIFY_LISTENERS);
return true;
}
// for two-tall blocks (like doors) we have to update it's sibling
boolean lower = newState.get(Properties.DOUBLE_BLOCK_HALF) == DoubleBlockHalf.LOWER;
BlockPos other = lower ? pos.up() : pos.down();
if (world.getBlockState(other).isOf(state.getBlock())) {
world.setBlockState(other, newState.with(Properties.DOUBLE_BLOCK_HALF, lower ? DoubleBlockHalf.UPPER : DoubleBlockHalf.LOWER), 16 | 2);
world.setBlockState(pos, newState, 16 | 2);
world.setBlockState(other, newState.with(Properties.DOUBLE_BLOCK_HALF, lower ? DoubleBlockHalf.UPPER : DoubleBlockHalf.LOWER), Block.FORCE_STATE | Block.NOTIFY_LISTENERS);
world.setBlockState(pos, newState, Block.FORCE_STATE | Block.NOTIFY_LISTENERS);
return true;
}

View file

@ -1,88 +0,0 @@
package com.minelittlepony.unicopia.block.state;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.block.Material;
import net.minecraft.state.property.Property;
import net.minecraft.tag.TagKey;
import net.minecraft.world.World;
/**
* A collection of block-state mappings.
*
*/
class BlockStateMap implements BlockStateConverter {
private final List<StateMapping> mappings;
BlockStateMap(List<StateMapping> mappings) {
this.mappings = new ArrayList<>(mappings);
}
@Override
public boolean canConvert(@Nullable BlockState state) {
return state != null && mappings.stream().anyMatch(i -> i.test(state));
}
@Override
@NotNull
public BlockState getConverted(World world, @NotNull BlockState state) {
for (StateMapping i : mappings) {
if (i.test(state)) {
return i.apply(world, state);
}
}
return state;
}
public static class Builder {
protected final ArrayList<StateMapping> items = new ArrayList<>();
public Builder add(StateMapping mapping) {
items.add(mapping);
return this;
}
public Builder removeBlock(Predicate<BlockState> mapper) {
return add(StateMapping.removeBlock(mapper));
}
public Builder removeBlock(Block from) {
return add(StateMapping.removeBlock(s -> s.isOf(from)));
}
public Builder replaceMaterial(Material from, Block to) {
return add(StateMapping.replaceMaterial(from, to));
}
public Builder replaceBlock(Block from, Block to) {
return add(StateMapping.replaceBlock(from, to));
}
public Builder replaceBlock(TagKey<Block> from, Block to) {
return add(StateMapping.replaceBlock(from, to));
}
public <T extends Comparable<T>> Builder replaceProperty(Block block, Property<T> property, T from, T to) {
return add(StateMapping.replaceProperty(block, property, from, to));
}
public <T extends Comparable<T>> Builder setProperty(Block block, Property<T> property, T to) {
return add(StateMapping.build(
s -> s.isOf(block),
(w, s) -> s.with(property, to)));
}
@SuppressWarnings("unchecked")
public <T extends BlockStateConverter> T build() {
return (T)new BlockStateMap(items);
}
}
}

View file

@ -0,0 +1,93 @@
package com.minelittlepony.unicopia.block.state;
import java.util.*;
import java.util.function.Predicate;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import com.google.gson.*;
import net.minecraft.block.BlockState;
import net.minecraft.util.JsonHelper;
import net.minecraft.world.World;
public class JsonReversableBlockStateConverter implements ReversableBlockStateConverter {
private final List<BlockStateConverter> entries;
@Nullable
private ReversableBlockStateConverter inverse;
public JsonReversableBlockStateConverter(JsonElement json) {
this(new ArrayList<>(), null);
JsonHelper.getArray(json.getAsJsonObject(), "entries").forEach(entry -> {
entries.add(Entry.of(entry.getAsJsonObject(), true));
});
}
public JsonReversableBlockStateConverter(List<BlockStateConverter> entries, @Nullable ReversableBlockStateConverter inverse) {
this.inverse = inverse;
this.entries = entries;
}
@Override
public boolean canConvert(@Nullable BlockState state) {
return entries.stream().anyMatch(entry -> entry.canConvert(state));
}
@Override
public @NotNull BlockState getConverted(World world, @NotNull BlockState state) {
return entries.stream().filter(entry -> entry.canConvert(state))
.findFirst()
.map(entry -> entry.getConverted(world, state))
.orElse(state);
}
@Override
public BlockStateConverter getInverse() {
if (inverse == null) {
inverse = new JsonReversableBlockStateConverter(entries.stream()
.filter(entry -> entry instanceof ReversableBlockStateConverter)
.map(entry -> ((ReversableBlockStateConverter)entry).getInverse())
.filter(Objects::nonNull)
.toList(), this);
}
return inverse;
}
record Entry (
Predicate<BlockState> match,
StateChange stateChange,
Optional<Entry> inverse
) implements ReversableBlockStateConverter {
public static Entry of(JsonObject json, boolean allowInversion) {
return new Entry(
StatePredicate.of(json.get("match")),
StateChange.fromJson(JsonHelper.getObject(json, "apply")),
allowInversion && json.has("inverse") ? Optional.of(of(JsonHelper.getObject(json, "inverse"), false)) : Optional.empty()
);
}
@Override
public boolean canConvert(@Nullable BlockState state) {
return state != null && match.test(state);
}
@Override
public @NotNull BlockState getConverted(World world, @NotNull BlockState state) {
return stateChange.getConverted(world, state);
}
@Nullable
@Override
public BlockStateConverter getInverse() {
return inverse.orElseGet(() -> {
return stateChange.getInverse()
.flatMap(invertedMatch -> StatePredicate.getInverse(match)
.map(invertedStateChange -> new Entry(invertedMatch, invertedStateChange, Optional.of(this))))
.orElse(null);
});
}
}
}

View file

@ -1,26 +0,0 @@
package com.minelittlepony.unicopia.block.state;
import java.util.List;
import java.util.stream.Collectors;
class ReversableBlockStateMap extends BlockStateMap implements ReversableBlockStateConverter {
private final BlockStateMap inverse;
ReversableBlockStateMap(List<StateMapping> mappings) {
super(mappings);
inverse = new BlockStateMap(mappings.stream().map(StateMapping::inverse).collect(Collectors.toList()));
}
@Override
public BlockStateMap getInverse() {
return inverse;
}
public static class Builder extends BlockStateMap.Builder {
@Override
@SuppressWarnings("unchecked")
public <T extends BlockStateConverter> T build() {
return (T)new ReversableBlockStateMap(items);
}
}
}

View file

@ -0,0 +1,117 @@
package com.minelittlepony.unicopia.block.state;
import java.util.*;
import java.util.function.Function;
import java.util.function.Predicate;
import org.jetbrains.annotations.NotNull;
import com.google.gson.JsonObject;
import com.minelittlepony.unicopia.Unicopia;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.state.property.Property;
import net.minecraft.util.Identifier;
import net.minecraft.util.JsonHelper;
import net.minecraft.util.registry.Registry;
import net.minecraft.world.World;
public abstract class StateChange {
private static final Map<Identifier, Function<JsonObject, StateChange>> SERIALIZERS = new HashMap<>();
static {
SERIALIZERS.put(Unicopia.id("set_state"), json -> {
final String sstate = JsonHelper.getString(json, "state");
final Identifier id = new Identifier(sstate);
final float chance = JsonHelper.getFloat(json, "chance", -1);
return new StateChange() {
@Override
public Optional<Predicate<BlockState>> getInverse() {
final StateChange self = this;
final Predicate<BlockState> test = StatePredicate.ofState(sstate);
return Optional.of(new StatePredicate() {
@Override
public StateChange getInverse() {
return self;
}
@Override
public boolean test(BlockState state) {
return test.test(state);
}
});
}
@Override
public @NotNull BlockState getConverted(World world, @NotNull BlockState state) {
if (chance > 0 && world.random.nextFloat() > chance) {
return state;
}
return Registry.BLOCK.getOrEmpty(id).map(Block::getDefaultState)
.map(newState -> merge(newState, state))
.orElse(state);
}
};
});
SERIALIZERS.put(Unicopia.id("set_property"), json -> {
final String name = JsonHelper.getString(json, "property");
final String value = json.get("value").getAsString();
final float chance = JsonHelper.getFloat(json, "chance", -1);
return new StateChange() {
@Override
public @NotNull BlockState getConverted(World world, @NotNull BlockState state) {
if (chance > 0 && world.random.nextFloat() > chance) {
return state;
}
return StatePredicate.getProperty(state, name).flatMap(property -> {
return property.parse(value).map(v -> state.with(property, v));
}).orElse(state);
}
};
});
SERIALIZERS.put(Unicopia.id("cycle_property"), json -> {
final String name = JsonHelper.getString(json, "property");
final float chance = JsonHelper.getFloat(json, "chance", -1);
return new StateChange() {
@Override
public @NotNull BlockState getConverted(World world, @NotNull BlockState state) {
if (chance > 0 && world.random.nextFloat() > chance) {
return state;
}
return StatePredicate.getProperty(state, name).map(property -> state.cycle(property)).orElse(state);
}
};
});
}
public Optional<Predicate<BlockState>> getInverse() {
return Optional.empty();
}
public abstract @NotNull BlockState getConverted(World world, @NotNull BlockState state);
public static StateChange fromJson(JsonObject json) {
String action = JsonHelper.getString(json, "action");
return Optional.of(SERIALIZERS.get(new Identifier(action))).map(serializer -> {
return serializer.apply(json);
}).orElseThrow(() -> new IllegalArgumentException("Invalid action " + action));
}
private static BlockState merge(BlockState into, BlockState from) {
for (var property : from.getProperties()) {
if (into.contains(property)) {
into = copy(into, from, property);
}
}
return into;
}
private static <T extends Comparable<T>> BlockState copy(BlockState to, BlockState from, Property<T> property) {
return to.with(property, from.get(property));
}
}

View file

@ -0,0 +1,79 @@
package com.minelittlepony.unicopia.block.state;
import java.util.*;
import java.util.stream.Collectors;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import com.google.gson.JsonElement;
import com.minelittlepony.unicopia.Unicopia;
import com.minelittlepony.unicopia.util.Resources;
import net.fabricmc.fabric.api.resource.IdentifiableResourceReloadListener;
import net.minecraft.block.BlockState;
import net.minecraft.resource.JsonDataLoader;
import net.minecraft.resource.ResourceManager;
import net.minecraft.util.Identifier;
import net.minecraft.util.profiler.Profiler;
import net.minecraft.world.World;
public class StateMapLoader extends JsonDataLoader implements IdentifiableResourceReloadListener {
private static final Identifier ID = Unicopia.id("data/state_maps");
public static final StateMapLoader INSTANCE = new StateMapLoader();
private Map<Identifier, ReversableBlockStateConverter> converters = new HashMap<>();
public StateMapLoader() {
super(Resources.GSON, "state_maps");
}
@Override
public Identifier getFabricId() {
return ID;
}
@Override
protected void apply(Map<Identifier, JsonElement> data, ResourceManager manager, Profiler profiler) {
converters = data.entrySet().stream().collect(Collectors.toMap(
Map.Entry::getKey,
entry -> new JsonReversableBlockStateConverter(entry.getValue())
));
}
static class Indirect<T extends BlockStateConverter> implements ReversableBlockStateConverter {
private final Identifier id;
private final BlockStateConverter inverse;
public Indirect(Identifier id, Optional<BlockStateConverter> inverse) {
this.id = id;
this.inverse = inverse.orElseGet(() -> new StateMapLoader.Indirect<>(id, Optional.of(this)) {
@Override
public Optional<BlockStateConverter> get() {
return Optional.ofNullable(INSTANCE.converters.get(id)).map(map -> map.getInverse());
}
});
}
@Override
public boolean canConvert(@Nullable BlockState state) {
return get().filter(map -> map.canConvert(state)).isPresent();
}
@Override
public @NotNull BlockState getConverted(World world, @NotNull BlockState state) {
return get().map(map -> map.getConverted(world, state)).orElse(state);
}
@SuppressWarnings("unchecked")
public Optional<T> get() {
return Optional.ofNullable((T)INSTANCE.converters.get(id));
}
@Override
public BlockStateConverter getInverse() {
return inverse;
}
}
}

View file

@ -1,150 +0,0 @@
package com.minelittlepony.unicopia.block.state;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Predicate;
import org.jetbrains.annotations.NotNull;
import com.minelittlepony.unicopia.util.Registries;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.block.Blocks;
import net.minecraft.block.Material;
import net.minecraft.state.property.IntProperty;
import net.minecraft.state.property.Property;
import net.minecraft.tag.TagKey;
import net.minecraft.world.World;
interface StateMapping extends Predicate<BlockState>, BiFunction<World, BlockState, BlockState> {
static Predicate<BlockState> isOf(Block block) {
return s -> s.isOf(block);
}
static Predicate<BlockState> isOf(Material mat) {
return s -> s.getMaterial() == mat;
}
static StateMapping cycleProperty(Block block, IntProperty property, int stopAt) {
if (stopAt < 0) {
return build(
isOf(block),
(w, s) -> s.cycle(property)
);
}
return build(
isOf(block).and(state -> state.get(property) < stopAt),
(w, s) -> s.get(property) >= stopAt ? s : s.cycle(property)
);
}
static StateMapping replaceMaterial(Material mat, Block block) {
return build(isOf(mat), block);
}
static StateMapping removeBlock(Predicate<BlockState> mapper) {
return build(
mapper,
(w, s) -> Blocks.AIR.getDefaultState());
}
static StateMapping replaceBlock(TagKey<Block> tag, Block to) {
return build(
s -> s.isIn(tag),
(w, s) -> to.getDefaultState(),
s -> build(
p -> p.isOf(to),
(w, p) -> Registries.entriesForTag(w, tag).getRandom(w.random).get().value().getDefaultState()
)
);
}
@SuppressWarnings("unchecked")
static StateMapping replaceBlock(Block from, Block to) {
return build(
s -> s.isOf(from),
(w, s) -> {
BlockState newState = to.getDefaultState();
for (@SuppressWarnings("rawtypes") Property i : s.getProperties()) {
if (newState.contains(i)) {
newState = newState.with(i, s.get(i));
}
}
return newState;
},
s -> replaceBlock(to, from));
}
static <T extends Comparable<T>> StateMapping replaceProperty(Block block, Property<T> property, T from, T to) {
return build(
s -> s.isOf(block) && s.get(property) == from,
(w, s) -> s.with(property, to),
s -> replaceProperty(block, property, to, from));
}
static StateMapping build(Predicate<BlockState> predicate, Block result) {
return build(predicate, (w, s) -> result.getDefaultState());
}
static StateMapping build(Predicate<BlockState> predicate, BiFunction<World, BlockState, BlockState> converter) {
return build(predicate, converter, s -> s);
}
static StateMapping build(Predicate<BlockState> predicate, Block result, Function<StateMapping, StateMapping> inverter) {
return build(predicate, (w, s) -> result.getDefaultState(), inverter);
}
static StateMapping build(Predicate<BlockState> predicate, BiFunction<World, BlockState, BlockState> converter, Function<StateMapping, StateMapping> inverter) {
return new StateMapping() {
private StateMapping inverse;
@Override
public boolean test(BlockState state) {
return predicate.test(state);
}
@Override
public BlockState apply(World world, BlockState state) {
return converter.apply(world, state);
}
@Override
public StateMapping inverse() {
if (inverse == null) {
inverse = inverter.apply(this);
}
return inverse;
}
};
}
/**
* Checks if this state can be converted by this mapping
*
* @param state State to check
*
* @return True if the state can be converted
*/
@Override
boolean test(@NotNull BlockState state);
/**
* Converts the given state based on this mapping
*
* @param state State to convert
*
* @return The converted state
*/
@Override
@NotNull
BlockState apply(World world, @NotNull BlockState state);
/**
* Gets the inverse of this mapping if one exists. Otherwise returns itself.
*/
@NotNull
StateMapping inverse();
}

View file

@ -1,99 +1,15 @@
package com.minelittlepony.unicopia.block.state;
import java.util.function.Predicate;
import com.minelittlepony.unicopia.Unicopia;
import com.minelittlepony.unicopia.block.UBlocks;
import com.minelittlepony.unicopia.util.Registries;
import net.minecraft.block.Block;
import net.minecraft.block.BlockState;
import net.minecraft.block.Blocks;
import net.minecraft.block.FarmlandBlock;
import net.minecraft.block.Material;
import net.minecraft.block.OreBlock;
import net.minecraft.block.PlantBlock;
import net.minecraft.block.RedstoneWireBlock;
import net.minecraft.block.SnowBlock;
import net.minecraft.tag.BlockTags;
import net.minecraft.util.registry.Registry;
public interface StateMaps {
BlockStateConverter SNOW_PILED = of("snow_piled");
BlockStateConverter ICE_AFFECTED = of("ice");
BlockStateConverter SILVERFISH_AFFECTED = of("infestation");
BlockStateConverter FIRE_AFFECTED = of("fire");
ReversableBlockStateConverter HELLFIRE_AFFECTED = of("hellfire");
public class StateMaps {
private static final Registry<BlockStateConverter> REGISTRY = Registries.createSimple(Unicopia.id("state_map"));
public static final BlockStateConverter SNOW_PILED = register("snow_piled", new BlockStateMap.Builder()
.add(StateMapping.cycleProperty(Blocks.SNOW, SnowBlock.LAYERS, 7)));
public static final BlockStateConverter ICE_AFFECTED = register("ice", new BlockStateMap.Builder()
.replaceMaterial(Material.WATER, Blocks.FROSTED_ICE)
.replaceMaterial(Material.LAVA, UBlocks.FROSTED_OBSIDIAN)
.add(StateMapping.cycleProperty(Blocks.SNOW, SnowBlock.LAYERS, 7))
.replaceBlock(Blocks.FIRE, Blocks.AIR)
.setProperty(Blocks.REDSTONE_WIRE, RedstoneWireBlock.POWER, 0));
public static final BlockStateConverter SILVERFISH_AFFECTED = register("infestation", new BlockStateMap.Builder()
.replaceBlock(Blocks.CHISELED_STONE_BRICKS, Blocks.INFESTED_CHISELED_STONE_BRICKS)
.replaceBlock(Blocks.COBBLESTONE, Blocks.INFESTED_COBBLESTONE)
.replaceBlock(Blocks.CRACKED_STONE_BRICKS, Blocks.INFESTED_CRACKED_STONE_BRICKS)
.replaceBlock(Blocks.MOSSY_STONE_BRICKS, Blocks.INFESTED_MOSSY_STONE_BRICKS)
.replaceBlock(Blocks.STONE, Blocks.INFESTED_STONE)
.replaceBlock(Blocks.STONE_BRICKS, Blocks.INFESTED_STONE_BRICKS));
public static final BlockStateConverter FIRE_AFFECTED = register("fire", new BlockStateMap.Builder()
.removeBlock(Blocks.SNOW)
.removeBlock(Blocks.SNOW_BLOCK)
.removeBlock(StateMaps::isPlant)
.replaceBlock(BlockTags.ICE, Blocks.WATER)
.replaceBlock(Blocks.CLAY, Blocks.BROWN_CONCRETE)
.replaceBlock(Blocks.OBSIDIAN, Blocks.LAVA)
.replaceBlock(UBlocks.FROSTED_OBSIDIAN, Blocks.LAVA)
.replaceBlock(Blocks.GRASS_BLOCK, Blocks.DIRT)
.replaceBlock(Blocks.MOSSY_COBBLESTONE, Blocks.COBBLESTONE)
.replaceBlock(Blocks.MOSSY_COBBLESTONE_WALL, Blocks.COBBLESTONE_WALL)
.replaceBlock(Blocks.MOSSY_STONE_BRICKS, Blocks.STONE_BRICKS)
.replaceBlock(Blocks.INFESTED_MOSSY_STONE_BRICKS, Blocks.INFESTED_STONE_BRICKS)
.replaceBlock(Blocks.PODZOL, Blocks.COARSE_DIRT)
.setProperty(Blocks.FARMLAND, FarmlandBlock.MOISTURE, 0)
.add(StateMapping.build(isOf(Blocks.DIRT), (w, s) -> (w.random.nextFloat() <= 0.15 ? Blocks.COARSE_DIRT.getDefaultState() : s))));
public static final ReversableBlockStateConverter HELLFIRE_AFFECTED = register("hellfire", new ReversableBlockStateMap.Builder()
.replaceBlock(Blocks.GRASS_BLOCK, Blocks.WARPED_NYLIUM)
.replaceBlock(Blocks.STONE, Blocks.NETHERRACK)
.replaceBlock(Blocks.SAND, Blocks.SOUL_SAND)
.replaceBlock(Blocks.GRAVEL, Blocks.SOUL_SAND)
.replaceBlock(Blocks.DIRT, Blocks.SOUL_SOIL)
.replaceBlock(Blocks.COARSE_DIRT, Blocks.SOUL_SOIL)
.replaceBlock(Blocks.TORCH, Blocks.SOUL_TORCH)
.replaceBlock(Blocks.WALL_TORCH, Blocks.SOUL_WALL_TORCH)
.replaceBlock(Blocks.OAK_LOG, Blocks.WARPED_STEM)
.replaceBlock(Blocks.STRIPPED_OAK_LOG, Blocks.STRIPPED_WARPED_STEM)
.replaceBlock(Blocks.STRIPPED_OAK_WOOD, Blocks.STRIPPED_WARPED_HYPHAE)
.replaceBlock(Blocks.OAK_PLANKS, Blocks.WARPED_PLANKS)
.replaceBlock(Blocks.OAK_DOOR, Blocks.WARPED_DOOR)
.replaceBlock(Blocks.OAK_STAIRS, Blocks.WARPED_STAIRS)
.replaceBlock(Blocks.OAK_TRAPDOOR, Blocks.WARPED_TRAPDOOR)
.replaceBlock(Blocks.OAK_PRESSURE_PLATE, Blocks.WARPED_PRESSURE_PLATE)
.replaceBlock(Blocks.OAK_BUTTON, Blocks.WARPED_BUTTON)
.replaceBlock(Blocks.OAK_FENCE, Blocks.WARPED_FENCE)
.replaceBlock(Blocks.OAK_FENCE_GATE, Blocks.WARPED_FENCE_GATE)
.replaceBlock(BlockTags.LEAVES, Blocks.WARPED_HYPHAE)
.replaceMaterial(Material.WATER, Blocks.OBSIDIAN)
.add(StateMapping.build(StateMaps::isPlant, Blocks.NETHER_WART, s -> StateMapping.replaceBlock(Blocks.NETHER_WART, Blocks.GRASS)))
.add(StateMapping.build(s -> !s.isOf(Blocks.NETHER_QUARTZ_ORE) && isOre(s), Blocks.NETHER_QUARTZ_ORE, s -> StateMapping.replaceBlock(Blocks.NETHER_QUARTZ_ORE, Blocks.COAL_ORE))));
private static <T extends BlockStateConverter> T register(String name, BlockStateMap.Builder value) {
return Registry.register(REGISTRY, Unicopia.id(name), value.build());
private static ReversableBlockStateConverter of(String name) {
return BlockStateConverter.of(Unicopia.id(name));
}
static Predicate<BlockState> isOf(Block block) {
return s -> s.isOf(block);
}
static boolean isPlant(BlockState s) {
return s.getBlock() instanceof PlantBlock;
}
static boolean isOre(BlockState s) {
return s.getBlock() instanceof OreBlock;
}
public static void bootstrap() {}
}

View file

@ -0,0 +1,262 @@
package com.minelittlepony.unicopia.block.state;
import java.util.*;
import java.util.function.IntPredicate;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.jetbrains.annotations.NotNull;
import com.google.common.base.Predicates;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import net.minecraft.block.*;
import net.minecraft.state.property.Property;
import net.minecraft.tag.TagKey;
import net.minecraft.util.Identifier;
import net.minecraft.util.JsonHelper;
import net.minecraft.util.registry.Registry;
import net.minecraft.util.registry.RegistryEntry;
import net.minecraft.world.World;
public abstract class StatePredicate implements Predicate<BlockState> {
public abstract StateChange getInverse();
@Override
public abstract boolean test(BlockState state);
public static Optional<StateChange> getInverse(Predicate<BlockState> predicate) {
if (predicate instanceof StatePredicate p) {
return Optional.of(p.getInverse());
}
return Optional.empty();
}
public static Predicate<BlockState> of(JsonElement json) {
List<Predicate<BlockState>> predicates = new ArrayList<>();
if (json.isJsonArray()) {
json.getAsJsonArray().forEach(element -> predicates.add(of(element)));
if (predicates.isEmpty()) {
return Predicates.alwaysFalse();
}
return state -> predicates.stream().anyMatch(pred -> pred.test(state));
}
JsonObject o = json.getAsJsonObject();
if (o.has("state")) {
predicates.add(ofState(JsonHelper.getString(o, "state")));
}
if (o.has("tag")) {
Optional.of(JsonHelper.getString(o, "tag")).map(s -> TagKey.of(Registry.BLOCK_KEY, new Identifier(s))).ifPresent(tag -> {
predicates.add(new StatePredicate() {
@Override
public StateChange getInverse() {
final Optional<Predicate<BlockState>> self = Optional.of(this);
return new StateChange() {
@Override
public Optional<Predicate<BlockState>> getInverse() {
return self;
}
@Override
public @NotNull BlockState getConverted(World world, @NotNull BlockState state) {
return Registry.BLOCK.getOrCreateEntryList(tag)
.getRandom(world.random)
.map(RegistryEntry::value)
.map(Block::getDefaultState)
.orElse(state);
}
};
}
@Override
public boolean test(BlockState state) {
return state.isIn(tag);
}
});
});
}
if (o.has("builtin")) {
predicates.add(ofBuiltIn(JsonHelper.getString(o, "builtin")));
}
if (predicates.isEmpty()) {
return Predicates.alwaysFalse();
}
if (predicates.size() == 1) {
return predicates.get(0);
}
return allOf(predicates);
}
private static Predicate<BlockState> allOf(List<Predicate<BlockState>> predicates) {
return state -> {
return predicates.isEmpty() || predicates.stream().allMatch(p -> p.test(state));
};
}
private static Predicate<BlockState> ofBuiltIn(String type) {
switch (type) {
case "plants": return StatePredicate::isPlant;
case "ores": return StatePredicate::isOre;
default: throw new IllegalArgumentException("Invalid builtin type: " + type);
}
}
static boolean isPlant(BlockState s) {
return s.getBlock() instanceof PlantBlock;
}
static boolean isOre(BlockState s) {
return s.getBlock() instanceof OreBlock;
}
public static Predicate<BlockState> ofState(String state) {
Identifier id = new Identifier(state.split("{")[0]);
List<PropertyOp> properties = Optional.of(state)
.filter(s -> s.contains("{"))
.stream()
.flatMap(s -> Stream.of(s.split("{")[1].split("}")[0].split(",")))
.map(PropertyOp::of)
.filter(Optional::isPresent)
.map(Optional::get)
.collect(Collectors.toList());
if (properties.isEmpty()) {
return new StatePredicate() {
@Override
public StateChange getInverse() {
final Optional<Predicate<BlockState>> self = Optional.of(this);
return new StateChange() {
@Override
public Optional<Predicate<BlockState>> getInverse() {
return self;
}
@Override
public @NotNull BlockState getConverted(World world, @NotNull BlockState state) {
return Registry.BLOCK.getOrEmpty(id).map(Block::getDefaultState).orElse(state);
}
};
}
@Override
public boolean test(BlockState state) {
return Registry.BLOCK.getOrEmpty(id).filter(state::isOf).isPresent();
}
};
}
return new StatePredicate() {
@Override
public StateChange getInverse() {
final Optional<Predicate<BlockState>> self = Optional.of(this);
return new StateChange() {
@Override
public Optional<Predicate<BlockState>> getInverse() {
return self;
}
@Override
public @NotNull BlockState getConverted(World world, @NotNull BlockState state) {
return Registry.BLOCK.getOrEmpty(id).map(Block::getDefaultState).map(newState -> {
for (PropertyOp prop : properties) {
newState = prop.applyTo(world, newState);
}
return newState;
}).orElse(state);
}
};
}
@Override
public boolean test(BlockState state) {
return Registry.BLOCK.getOrEmpty(id).filter(state::isOf).isPresent() && properties.stream().allMatch(p -> p.test(state));
}
};
}
private record PropertyOp (String name, String value, Comparison op) implements Predicate<BlockState> {
public static Optional<PropertyOp> of(String pattern) {
String[] splitten = pattern.split("[=<>]", 2);
if (pattern.indexOf('=') == splitten[0].length()) {
return Optional.of(new PropertyOp(splitten[0], splitten[1], Comparison.EQUAL));
}
if (pattern.indexOf('<') == splitten[0].length()) {
return Optional.of(new PropertyOp(splitten[0], splitten[1], Comparison.LESS));
}
if (pattern.indexOf('>') == splitten[0].length()) {
return Optional.of(new PropertyOp(splitten[0], splitten[1], Comparison.GREATER));
}
return Optional.empty();
}
public BlockState applyTo(World world, BlockState state) {
return getProperty(state, name).flatMap(property -> {
return property.parse(value).map(val -> {
return applyValidValue(world, property, val, state);
});
}).orElse(state);
}
private <T extends Comparable<T>> BlockState applyValidValue(World world, Property<T> property, T allowedValue, BlockState state) {
if (op == Comparison.EQUAL) {
return state.with(property, allowedValue);
}
if (op == Comparison.GREATER) {
var allowedValues = property.getValues().stream().filter(v -> op.test(v.compareTo(allowedValue))).toList();
if (!allowedValues.contains(state.get(property))) {
int index = world.random.nextInt(allowedValues.size());
return state.with(property, allowedValues.remove(index));
}
}
return state;
}
@Override
public boolean test(BlockState state) {
return getProperty(state, name)
.flatMap(property -> property.parse(value)
.filter(v -> op.test(state.get(property).compareTo(v))))
.isPresent();
}
enum Comparison implements IntPredicate {
LESS {
@Override
public boolean test(int value) {
return value < 0;
}
},
GREATER {
@Override
public boolean test(int value) {
return value > 0;
}
},
EQUAL {
@Override
public boolean test(int value) {
return value == 0;
}
};
@Override
public abstract boolean test(int value);
}
}
@SuppressWarnings("unchecked")
public static <T extends Comparable<T>> Optional<Property<T>> getProperty(BlockState state, String name) {
return (Optional<Property<T>>)(Object)state.getProperties().stream()
.filter(property -> property.getName().contentEquals(name))
.findFirst();
}
}

View file

@ -0,0 +1,94 @@
{
"replace": false,
"entries": [
{
"match": [
{ "state": "minecraft:snow" },
{ "state": "minecraft:snow_block" },
{ "builtin": "plants" }
],
"apply": {
"action": "unicopia:set_state",
"state": "minecraft:air"
}
},
{
"match": { "state": "minecraft:ice" },
"apply": {
"action": "unicopia:set_state",
"state": "minecraft:water"
}
},
{
"match": { "state": "minecraft:clay" },
"apply": {
"action": "unicopia:set_state",
"state": "minecraft:brown_concrete"
}
},
{
"match": [
{ "state": "minecraft:obsidian" },
{ "state": "unicopia:frosted_obsidian" }
],
"apply": {
"action": "unicopia:set_state",
"state": "minecraft:lava"
}
},
{
"match": { "state": "minecraft:grass_block" },
"apply": {
"action": "unicopia:set_state",
"state": "minecraft:dirt"
}
},
{
"match": { "state": "minecraft:mossy_cobblestone" },
"apply": {
"action": "unicopia:set_state",
"state": "minecraft:cobblestone"
}
},
{
"match": { "state": "minecraft:mossy_cobblestone_wall" },
"apply": {
"action": "unicopia:set_state",
"state": "minecraft:cobblestone_wall"
}
},
{
"match": [
{ "state": "minecraft:mossy_stone_bricks" },
{ "state": "minecraft:infested_mossy_stone_bricks" }
],
"apply": {
"action": "unicopia:set_state",
"state": "minecraft:stone_bricks"
}
},
{
"match": { "state": "minecraft:podzol" },
"apply": {
"action": "unicopia:set_state",
"state": "minecraft:coarse_dirt"
}
},
{
"match": { "state": "minecraft:farmland" },
"apply": {
"action": "unicopia:set_property",
"property": "moisture",
"value": 0
}
},
{
"match": { "state": "minecraft:dirt" },
"apply": {
"action": "unicopia:set_state",
"state": "minecraft:coarse_dirt",
"chance": 0.15
}
}
]
}

View file

@ -0,0 +1,169 @@
{
"replace": false,
"entries": [
{
"match": { "state": "minecraft:grass_block" },
"apply": {
"action": "unicopia:set_state",
"state": "minecraft:warped_nylium"
}
},
{
"match": { "state": "minecraft:stone" },
"apply": {
"action": "unicopia:set_state",
"state": "minecraft:netherrack"
}
},
{
"match": { "state": "minecraft:sand" },
"apply": {
"action": "unicopia:set_state",
"state": "minecraft:soul_sand"
}
},
{
"match": [
{ "state": "minecraft:dirt" },
{ "state": "minecraft:coarse_dirt" }
],
"apply": {
"action": "unicopia:set_state",
"state": "minecraft:soul_soil"
}
},
{
"match": { "state": "minecraft:torch" },
"apply": {
"action": "unicopia:set_state",
"state": "minecraft:soul_torch"
}
},
{
"match": { "state": "minecraft:wall_torch" },
"apply": {
"action": "unicopia:set_state",
"state": "minecraft:soul_wall_torch"
}
},
{
"match": { "state": "minecraft:oak_log" },
"apply": {
"action": "unicopia:set_state",
"state": "minecraft:warped_stem"
}
},
{
"match": { "state": "minecraft:stripped_oak_log" },
"apply": {
"action": "unicopia:set_state",
"state": "minecraft:stripped_warped_stem"
}
},
{
"match": { "state": "minecraft:stripped_oak_wood" },
"apply": {
"action": "unicopia:set_state",
"state": "minecraft:stripped_warped_hyphae"
}
},
{
"match": { "state": "minecraft:oak_planks" },
"apply": {
"action": "unicopia:set_state",
"state": "minecraft:warped_planks"
}
},
{
"match": { "state": "minecraft:oak_door" },
"apply": {
"action": "unicopia:set_state",
"state": "minecraft:warped_door"
}
},
{
"match": { "state": "minecraft:oak_stairs" },
"apply": {
"action": "unicopia:set_state",
"state": "minecraft:warped_stairs"
}
},
{
"match": { "state": "minecraft:oak_trapdoor" },
"apply": {
"action": "unicopia:set_state",
"state": "minecraft:warped_trapdoor"
}
},
{
"match": { "state": "minecraft:oak_pressure_plate" },
"apply": {
"action": "unicopia:set_state",
"state": "minecraft:warped_pressure_plate"
}
},
{
"match": { "state": "minecraft:oak_button" },
"apply": {
"action": "unicopia:set_state",
"state": "minecraft:warped_button"
}
},
{
"match": { "state": "minecraft:oak_fence" },
"apply": {
"action": "unicopia:set_state",
"state": "minecraft:warped_fence"
}
},
{
"match": { "state": "minecraft:oak_fence_gate" },
"apply": {
"action": "unicopia:set_state",
"state": "minecraft:warped_fence_gate"
}
},
{
"match": { "tag": "minecraft:leaves" },
"apply": {
"action": "unicopia:set_state",
"state": "minecraft:warped_hyphae"
}
},
{
"match": { "state": "minecraft:water" },
"apply": {
"action": "unicopia:set_state",
"state": "minecraft:obsidian"
}
},
{
"match": { "builtin": "plants" },
"apply": {
"action": "unicopia:set_state",
"state": "minecraft:nether_wart"
},
"reverse": {
"match": { "state": "minecraft:nether_wart" },
"apply": {
"action": "unicopia:set_state",
"state": "minecraft:grass"
}
}
},
{
"match": { "builtin": "ores" },
"apply": {
"action": "unicopia:set_state",
"state": "minecraft:nether_quartz_ore"
},
"reverse": {
"match": { "state": "minecraft:nether_quartz_ore" },
"apply": {
"action": "unicopia:set_state",
"state": "minecraft:coal_ore"
}
}
}
]
}

View file

@ -0,0 +1,35 @@
{
"parent": "unicopia:snow_piled",
"replace": false,
"entries": [
{
"match": { "state": "minecraft:water" },
"apply": {
"action": "unicopia:set_state",
"state": "minecraft:frosted_ice"
}
},
{
"match": { "state": "minecraft:lava" },
"apply": {
"action": "unicopia:set_state",
"state": "unicopia:frosted_obsidian"
}
},
{
"match": { "state": "minecraft:fire" },
"apply": {
"action": "unicopia:set_state",
"state": "minecraft:air"
}
},
{
"match": { "state": "minecraft:redstone_wire" },
"apply": {
"action": "unicopia:set_property",
"property": "power",
"value": 0
}
}
]
}

View file

@ -0,0 +1,47 @@
{
"replace": false,
"entries": [
{
"match": { "state": "minecraft:chiseled_stone_bricks" },
"apply": {
"action": "unicopia:set_state",
"state": "minecraft:infested_chiseled_stone_bricks"
}
},
{
"match": { "state": "minecraft:cobblestone" },
"apply": {
"action": "unicopia:set_state",
"state": "minecraft:infested_cobblestone"
}
},
{
"match": { "state": "minecraft:cracked_stone_bricks" },
"apply": {
"action": "unicopia:set_state",
"state": "minecraft:infested_cracked_stone_bricks"
}
},
{
"match": { "state": "minecraft:mossy_stone_bricks" },
"apply": {
"action": "unicopia:set_state",
"state": "minecraft:infested_mossy_stone_bricks"
}
},
{
"match": { "state": "minecraft:stone" },
"apply": {
"action": "unicopia:set_state",
"state": "minecraft:infested_stone"
}
},
{
"match": { "state": "minecraft:stone_bricks" },
"apply": {
"action": "unicopia:set_state",
"state": "minecraft:infested_stone_bricks"
}
}
]
}

View file

@ -0,0 +1,12 @@
{
"replace": false,
"entries": [
{
"match": { "state": "minecraft:snow{layers<7}" },
"apply": {
"action": "unicopia:cycle_property",
"property": "layers"
}
}
]
}