From 1de93136103643f20adc085518c97c2d31f1209a Mon Sep 17 00:00:00 2001 From: Sollace Date: Mon, 29 Aug 2022 00:47:39 +0200 Subject: [PATCH] Move state maps to resources --- .../com/minelittlepony/unicopia/Unicopia.java | 2 - .../block/state/BlockStateConverter.java | 16 +- .../unicopia/block/state/BlockStateMap.java | 88 ------ .../JsonReversableBlockStateConverter.java | 93 +++++++ .../block/state/ReversableBlockStateMap.java | 26 -- .../unicopia/block/state/StateChange.java | 117 ++++++++ .../unicopia/block/state/StateMapLoader.java | 79 ++++++ .../unicopia/block/state/StateMapping.java | 150 ---------- .../unicopia/block/state/StateMaps.java | 100 +------ .../unicopia/block/state/StatePredicate.java | 262 ++++++++++++++++++ .../data/unicopia/state_maps/fire.json | 94 +++++++ .../data/unicopia/state_maps/hellfire.json | 169 +++++++++++ .../data/unicopia/state_maps/ice.json | 35 +++ .../data/unicopia/state_maps/infestation.json | 47 ++++ .../data/unicopia/state_maps/snow_piled.json | 12 + 15 files changed, 929 insertions(+), 361 deletions(-) delete mode 100644 src/main/java/com/minelittlepony/unicopia/block/state/BlockStateMap.java create mode 100644 src/main/java/com/minelittlepony/unicopia/block/state/JsonReversableBlockStateConverter.java delete mode 100644 src/main/java/com/minelittlepony/unicopia/block/state/ReversableBlockStateMap.java create mode 100644 src/main/java/com/minelittlepony/unicopia/block/state/StateChange.java create mode 100644 src/main/java/com/minelittlepony/unicopia/block/state/StateMapLoader.java delete mode 100644 src/main/java/com/minelittlepony/unicopia/block/state/StateMapping.java create mode 100644 src/main/java/com/minelittlepony/unicopia/block/state/StatePredicate.java create mode 100644 src/main/resources/data/unicopia/state_maps/fire.json create mode 100644 src/main/resources/data/unicopia/state_maps/hellfire.json create mode 100644 src/main/resources/data/unicopia/state_maps/ice.json create mode 100644 src/main/resources/data/unicopia/state_maps/infestation.json create mode 100644 src/main/resources/data/unicopia/state_maps/snow_piled.json diff --git a/src/main/java/com/minelittlepony/unicopia/Unicopia.java b/src/main/java/com/minelittlepony/unicopia/Unicopia.java index b8f0093b..5ed704c9 100644 --- a/src/main/java/com/minelittlepony/unicopia/Unicopia.java +++ b/src/main/java/com/minelittlepony/unicopia/Unicopia.java @@ -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(); } diff --git a/src/main/java/com/minelittlepony/unicopia/block/state/BlockStateConverter.java b/src/main/java/com/minelittlepony/unicopia/block/state/BlockStateConverter.java index e0991d87..e34905e0 100644 --- a/src/main/java/com/minelittlepony/unicopia/block/state/BlockStateConverter.java +++ b/src/main/java/com/minelittlepony/unicopia/block/state/BlockStateConverter.java @@ -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(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; } diff --git a/src/main/java/com/minelittlepony/unicopia/block/state/BlockStateMap.java b/src/main/java/com/minelittlepony/unicopia/block/state/BlockStateMap.java deleted file mode 100644 index fec1ff86..00000000 --- a/src/main/java/com/minelittlepony/unicopia/block/state/BlockStateMap.java +++ /dev/null @@ -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 mappings; - - BlockStateMap(List 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 items = new ArrayList<>(); - - public Builder add(StateMapping mapping) { - items.add(mapping); - return this; - } - - public Builder removeBlock(Predicate 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 from, Block to) { - return add(StateMapping.replaceBlock(from, to)); - } - - public > Builder replaceProperty(Block block, Property property, T from, T to) { - return add(StateMapping.replaceProperty(block, property, from, to)); - } - - public > Builder setProperty(Block block, Property property, T to) { - return add(StateMapping.build( - s -> s.isOf(block), - (w, s) -> s.with(property, to))); - } - - @SuppressWarnings("unchecked") - public T build() { - return (T)new BlockStateMap(items); - } - } -} diff --git a/src/main/java/com/minelittlepony/unicopia/block/state/JsonReversableBlockStateConverter.java b/src/main/java/com/minelittlepony/unicopia/block/state/JsonReversableBlockStateConverter.java new file mode 100644 index 00000000..86f3ecf9 --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/block/state/JsonReversableBlockStateConverter.java @@ -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 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 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 match, + StateChange stateChange, + Optional 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); + }); + } + } +} diff --git a/src/main/java/com/minelittlepony/unicopia/block/state/ReversableBlockStateMap.java b/src/main/java/com/minelittlepony/unicopia/block/state/ReversableBlockStateMap.java deleted file mode 100644 index a2a2cb3b..00000000 --- a/src/main/java/com/minelittlepony/unicopia/block/state/ReversableBlockStateMap.java +++ /dev/null @@ -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 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 build() { - return (T)new ReversableBlockStateMap(items); - } - } -} diff --git a/src/main/java/com/minelittlepony/unicopia/block/state/StateChange.java b/src/main/java/com/minelittlepony/unicopia/block/state/StateChange.java new file mode 100644 index 00000000..28807c20 --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/block/state/StateChange.java @@ -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> 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> getInverse() { + final StateChange self = this; + final Predicate 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> 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 > BlockState copy(BlockState to, BlockState from, Property property) { + return to.with(property, from.get(property)); + } +} diff --git a/src/main/java/com/minelittlepony/unicopia/block/state/StateMapLoader.java b/src/main/java/com/minelittlepony/unicopia/block/state/StateMapLoader.java new file mode 100644 index 00000000..d891d546 --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/block/state/StateMapLoader.java @@ -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 converters = new HashMap<>(); + + public StateMapLoader() { + super(Resources.GSON, "state_maps"); + } + + @Override + public Identifier getFabricId() { + return ID; + } + + @Override + protected void apply(Map data, ResourceManager manager, Profiler profiler) { + converters = data.entrySet().stream().collect(Collectors.toMap( + Map.Entry::getKey, + entry -> new JsonReversableBlockStateConverter(entry.getValue()) + )); + } + + static class Indirect implements ReversableBlockStateConverter { + private final Identifier id; + private final BlockStateConverter inverse; + + public Indirect(Identifier id, Optional inverse) { + this.id = id; + this.inverse = inverse.orElseGet(() -> new StateMapLoader.Indirect<>(id, Optional.of(this)) { + @Override + public Optional 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 get() { + return Optional.ofNullable((T)INSTANCE.converters.get(id)); + } + + @Override + public BlockStateConverter getInverse() { + return inverse; + } + } +} diff --git a/src/main/java/com/minelittlepony/unicopia/block/state/StateMapping.java b/src/main/java/com/minelittlepony/unicopia/block/state/StateMapping.java deleted file mode 100644 index 3440ede1..00000000 --- a/src/main/java/com/minelittlepony/unicopia/block/state/StateMapping.java +++ /dev/null @@ -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, BiFunction { - - static Predicate isOf(Block block) { - return s -> s.isOf(block); - } - - static Predicate 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 mapper) { - return build( - mapper, - (w, s) -> Blocks.AIR.getDefaultState()); - } - - static StateMapping replaceBlock(TagKey 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 > StateMapping replaceProperty(Block block, Property 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 predicate, Block result) { - return build(predicate, (w, s) -> result.getDefaultState()); - } - - static StateMapping build(Predicate predicate, BiFunction converter) { - return build(predicate, converter, s -> s); - } - - static StateMapping build(Predicate predicate, Block result, Function inverter) { - return build(predicate, (w, s) -> result.getDefaultState(), inverter); - } - - static StateMapping build(Predicate predicate, BiFunction converter, Function 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(); -} diff --git a/src/main/java/com/minelittlepony/unicopia/block/state/StateMaps.java b/src/main/java/com/minelittlepony/unicopia/block/state/StateMaps.java index a7d38b81..d2f238eb 100644 --- a/src/main/java/com/minelittlepony/unicopia/block/state/StateMaps.java +++ b/src/main/java/com/minelittlepony/unicopia/block/state/StateMaps.java @@ -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 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 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 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() {} } diff --git a/src/main/java/com/minelittlepony/unicopia/block/state/StatePredicate.java b/src/main/java/com/minelittlepony/unicopia/block/state/StatePredicate.java new file mode 100644 index 00000000..24f080fb --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/block/state/StatePredicate.java @@ -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 { + + public abstract StateChange getInverse(); + + @Override + public abstract boolean test(BlockState state); + + public static Optional getInverse(Predicate predicate) { + if (predicate instanceof StatePredicate p) { + return Optional.of(p.getInverse()); + } + return Optional.empty(); + } + + public static Predicate of(JsonElement json) { + + List> 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> self = Optional.of(this); + return new StateChange() { + @Override + public Optional> 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 allOf(List> predicates) { + return state -> { + return predicates.isEmpty() || predicates.stream().allMatch(p -> p.test(state)); + }; + } + + private static Predicate 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 ofState(String state) { + Identifier id = new Identifier(state.split("{")[0]); + List 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> self = Optional.of(this); + return new StateChange() { + @Override + public Optional> 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> self = Optional.of(this); + return new StateChange() { + @Override + public Optional> 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 { + public static Optional 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 > BlockState applyValidValue(World world, Property 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 > Optional> getProperty(BlockState state, String name) { + return (Optional>)(Object)state.getProperties().stream() + .filter(property -> property.getName().contentEquals(name)) + .findFirst(); + } +} diff --git a/src/main/resources/data/unicopia/state_maps/fire.json b/src/main/resources/data/unicopia/state_maps/fire.json new file mode 100644 index 00000000..8d051e68 --- /dev/null +++ b/src/main/resources/data/unicopia/state_maps/fire.json @@ -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 + } + } + ] +} diff --git a/src/main/resources/data/unicopia/state_maps/hellfire.json b/src/main/resources/data/unicopia/state_maps/hellfire.json new file mode 100644 index 00000000..2695cbab --- /dev/null +++ b/src/main/resources/data/unicopia/state_maps/hellfire.json @@ -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" + } + } + } + ] +} diff --git a/src/main/resources/data/unicopia/state_maps/ice.json b/src/main/resources/data/unicopia/state_maps/ice.json new file mode 100644 index 00000000..04ec142c --- /dev/null +++ b/src/main/resources/data/unicopia/state_maps/ice.json @@ -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 + } + } + ] +} diff --git a/src/main/resources/data/unicopia/state_maps/infestation.json b/src/main/resources/data/unicopia/state_maps/infestation.json new file mode 100644 index 00000000..4e8b0ab2 --- /dev/null +++ b/src/main/resources/data/unicopia/state_maps/infestation.json @@ -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" + } + } + ] +} diff --git a/src/main/resources/data/unicopia/state_maps/snow_piled.json b/src/main/resources/data/unicopia/state_maps/snow_piled.json new file mode 100644 index 00000000..f9b3e6d5 --- /dev/null +++ b/src/main/resources/data/unicopia/state_maps/snow_piled.json @@ -0,0 +1,12 @@ +{ + "replace": false, + "entries": [ + { + "match": { "state": "minecraft:snow{layers<7}" }, + "apply": { + "action": "unicopia:cycle_property", + "property": "layers" + } + } + ] +}