diff --git a/src/main/java/com/fusionflux/portalcubed/blocks/PortalBlocksLoader.java b/src/main/java/com/fusionflux/portalcubed/blocks/PortalBlocksLoader.java index 3bb70942..c4e69d06 100644 --- a/src/main/java/com/fusionflux/portalcubed/blocks/PortalBlocksLoader.java +++ b/src/main/java/com/fusionflux/portalcubed/blocks/PortalBlocksLoader.java @@ -6,6 +6,7 @@ import com.google.gson.JsonElement; import com.google.gson.JsonObject; import net.fabricmc.api.EnvType; +import net.fabricmc.fabric.api.itemgroup.v1.ItemGroupEvents; import net.minecraft.client.renderer.RenderType; import net.minecraft.core.Registry; import net.minecraft.resources.ResourceLocation; @@ -25,7 +26,6 @@ import org.quiltmc.loader.api.minecraft.MinecraftQuiltLoader; import org.quiltmc.qsl.block.extensions.api.QuiltBlockSettings; import org.quiltmc.qsl.block.extensions.api.client.BlockRenderLayerMap; -import org.quiltmc.qsl.item.group.api.QuiltItemGroup; import java.io.IOException; import java.io.Reader; @@ -52,6 +52,7 @@ public final class PortalBlocksLoader { @ClientOnly private static Map renderLayers; private static final Map BLOCK_DATA = new LinkedHashMap<>(); + ItemGroupEvents private static final CreativeModeTab ITEM_GROUP = QuiltItemGroup.createWithIcon( id("portal_blocks"), () -> new ItemStack(PortalCubedItems.BLOCK_ITEM_ICON) diff --git a/src/main/java/com/fusionflux/portalcubed/client/PortalCubedClient.java b/src/main/java/com/fusionflux/portalcubed/client/PortalCubedClient.java index 4be2efcd..ba7905c0 100644 --- a/src/main/java/com/fusionflux/portalcubed/client/PortalCubedClient.java +++ b/src/main/java/com/fusionflux/portalcubed/client/PortalCubedClient.java @@ -26,6 +26,7 @@ import com.fusionflux.portalcubed.fog.FogSettings; import com.fusionflux.portalcubed.items.PortalCubedItems; import com.fusionflux.portalcubed.items.PortalGun; +import com.fusionflux.portalcubed.items.PortalTabsLoader; import com.fusionflux.portalcubed.mixin.client.AbstractSoundInstanceAccessor; import com.fusionflux.portalcubed.mixin.client.DeathScreenAccessor; import com.fusionflux.portalcubed.mixin.client.MusicManagerAccessor; @@ -428,6 +429,8 @@ Items.LIGHT, new ResourceLocation("level") // } // }); + PortalTabsLoader.load(mod); + try { final CompoundTag compound = NbtIo.readCompressed(GLOBAL_ADVANCEMENTS_FILE); for (final Tag element : compound.getList("Advancements", Tag.TAG_STRING)) { diff --git a/src/main/java/com/fusionflux/portalcubed/items/PortalTabsLoader.java b/src/main/java/com/fusionflux/portalcubed/items/PortalTabsLoader.java new file mode 100644 index 00000000..0cbe1d88 --- /dev/null +++ b/src/main/java/com/fusionflux/portalcubed/items/PortalTabsLoader.java @@ -0,0 +1,152 @@ +package com.fusionflux.portalcubed.items; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.mojang.brigadier.StringReader; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import it.unimi.dsi.fastutil.Pair; +import net.fabricmc.fabric.api.itemgroup.v1.FabricItemGroup; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.TagParser; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.util.GsonHelper; +import net.minecraft.world.flag.FeatureFlagSet; +import net.minecraft.world.flag.FeatureFlags; +import net.minecraft.world.item.CreativeModeTab; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; +import org.quiltmc.loader.api.ModContainer; +import org.quiltmc.loader.api.minecraft.ClientOnly; + +import java.io.IOException; +import java.io.Reader; +import java.io.UncheckedIOException; +import java.nio.file.Files; +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.function.Supplier; + +import static com.fusionflux.portalcubed.PortalCubed.id; + +@ClientOnly +public class PortalTabsLoader { + private static final Map>> CONDITION_TYPES = Map.of( + "and", o -> GsonHelper.getAsJsonArray(o, "conditions") + .asList() + .stream() + .map(PortalTabsLoader::parseCondition) + .reduce(Predicate::and) + .orElse(e -> true), + "or", o -> GsonHelper.getAsJsonArray(o, "conditions") + .asList() + .stream() + .map(PortalTabsLoader::parseCondition) + .reduce(Predicate::or) + .orElse(e -> false), + "not", o -> parseCondition(o.get("condition")).negate(), + "hasPermissions", o -> CreativeModeTab.ItemDisplayParameters::hasPermissions, + "hasFeatures", o -> { + final FeatureFlagSet flags = FeatureFlags.REGISTRY.fromNames( + GsonHelper.getAsJsonArray(o, "flags") + .asList() + .stream() + .map(e -> new ResourceLocation(GsonHelper.convertToString(e, "flag"))) + ::iterator + ); + return p -> flags.isSubsetOf(p.enabledFeatures()); + } + ); + + public static void load(ModContainer mod) { + final JsonObject jsonObject; + try (Reader reader = Files.newBufferedReader(mod.getPath("portal_tabs.json"))) { + jsonObject = GsonHelper.parse(reader); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + load(jsonObject); + } + + private static void load(JsonObject jsonObject) { + for (final var entry : jsonObject.entrySet()) { + final CreativeModeTab.Builder builder = FabricItemGroup.builder(id(entry.getKey())); + final JsonObject entryData = GsonHelper.convertToJsonObject(entry.getValue(), "tab"); + if (entryData.has("icon")) { + builder.icon(parseItemStack(entryData.get("icon"), "icon")); + } + if (entryData.has("rightAlign") && GsonHelper.getAsBoolean(entryData, "rightAlign")) { + builder.alignedRight(); + } + if (entryData.has("showTitle") && !GsonHelper.getAsBoolean(entryData, "showTitle")) { + builder.hideTitle(); + } + if (entryData.has("scrollBar") && !GsonHelper.getAsBoolean(entryData, "scrollBar")) { + builder.noScrollBar(); + } + if (entryData.has("backgroundImage")) { + builder.backgroundSuffix(GsonHelper.getAsString(entryData, "backgroundImage")); + } + if (entryData.has("items") || entryData.has("itemConditions")) { + final List items = parseItemArray(entryData); + final var itemConditions = GsonHelper.getAsJsonArray(entryData, "itemConditions", new JsonArray()) + .asList() + .stream() + .map(e -> Pair.of(parseCondition(e), parseItemArray((JsonObject)e))) + .toList(); + builder.displayItems((itemDisplayParameters, output) -> { + output.acceptAll(items); + for (final var condition : itemConditions) { + if (condition.key().test(itemDisplayParameters)) { + output.acceptAll(condition.value()); + } + } + }); + } + builder.build(); + } + } + + private static List parseItemArray(JsonObject owner) { + return GsonHelper.getAsJsonArray(owner, "items", new JsonArray()) + .asList() + .stream() + .map(e -> parseItemStack(e, "item").get()) + .toList(); + } + + private static Supplier parseItemStack(JsonElement element, String memberName) { + if (element.isJsonPrimitive()) { + final Item item = BuiltInRegistries.ITEM.get( + new ResourceLocation(GsonHelper.convertToString(element, memberName)) + ); + return () -> new ItemStack(item); + } + final JsonObject object = GsonHelper.convertToJsonObject(element, memberName); + final Item item = BuiltInRegistries.ITEM.get(new ResourceLocation(GsonHelper.getAsString(object, "id"))); + final int count = object.has("count") ? GsonHelper.getAsInt(object, "count") : 1; + final CompoundTag tag; + try { + tag = object.has("nbt") + ? new TagParser(new StringReader(GsonHelper.getAsString(object, "nbt"))).readStruct() + : null; + } catch (CommandSyntaxException e) { + throw new RuntimeException(e); + } + return () -> { + final ItemStack stack = new ItemStack(item, count); + if (tag != null) { + stack.setTag(tag); + } + return stack; + }; + } + + private static Predicate parseCondition(JsonElement element) { + final JsonObject object = GsonHelper.convertToJsonObject(element, "condition"); + return CONDITION_TYPES.get(GsonHelper.getAsString(object, "type")).apply(object); + } +} diff --git a/src/main/resources/portal_tabs.json b/src/main/resources/portal_tabs.json new file mode 100644 index 00000000..e69de29b