From c7a23a19d9c723f2ea42ea364436f8a74be635b3 Mon Sep 17 00:00:00 2001 From: Sollace Date: Sat, 3 Feb 2024 23:17:45 +0000 Subject: [PATCH] Add pages for the altar and the spectral clock --- .../unicopia/block/state/StateUtil.java | 52 +++++ .../unicopia/client/gui/UHud.java | 1 + .../gui/spellbook/element/DynamicContent.java | 6 + .../gui/spellbook/element/PageElement.java | 1 + .../gui/spellbook/element/Structure.java | 181 ++++++++++++++++++ .../gui/spellbook/element/TextBlock.java | 3 +- .../container/SpellbookChapterLoader.java | 71 ++++++- .../resources/assets/unicopia/lang/en_us.json | 7 + .../spellbook/chapters/crystal_heart.json | 82 ++++++-- 9 files changed, 389 insertions(+), 15 deletions(-) create mode 100644 src/main/java/com/minelittlepony/unicopia/client/gui/spellbook/element/Structure.java diff --git a/src/main/java/com/minelittlepony/unicopia/block/state/StateUtil.java b/src/main/java/com/minelittlepony/unicopia/block/state/StateUtil.java index 42334368..1e3be2e9 100644 --- a/src/main/java/com/minelittlepony/unicopia/block/state/StateUtil.java +++ b/src/main/java/com/minelittlepony/unicopia/block/state/StateUtil.java @@ -1,11 +1,23 @@ package com.minelittlepony.unicopia.block.state; +import java.util.Iterator; + import org.jetbrains.annotations.Nullable; +import com.google.common.base.CharMatcher; +import com.google.common.base.Splitter; +import net.minecraft.block.Block; import net.minecraft.block.BlockState; +import net.minecraft.block.Blocks; +import net.minecraft.registry.Registries; import net.minecraft.state.property.Property; +import net.minecraft.util.Identifier; public interface StateUtil { + Splitter COMMA_SPLITTER = Splitter.on(','); + Splitter KEY_VALUE_SPLITTER = Splitter.on('=').limit(2); + Splitter STATE_SPLITTER = Splitter.on(CharMatcher.anyOf("[]")).limit(2); + @SuppressWarnings({ "unchecked", "rawtypes" }) static BlockState copyState(BlockState from, @Nullable BlockState to) { if (to == null) { @@ -16,4 +28,44 @@ public interface StateUtil { } return to; } + + static BlockState stateFromString(String string) { + Iterator pair = Splitter.on(CharMatcher.anyOf("[]")).limit(3).split(string).iterator(); + if (!pair.hasNext()) { + return Blocks.AIR.getDefaultState(); + } + Block block = Identifier.validate(pair.next()).result().map(Registries.BLOCK::get).orElse(null); + if (block == null) { + return Blocks.AIR.getDefaultState(); + } + if (!pair.hasNext()) { + return block.getDefaultState(); + } + return stateFromKeyMap(block, pair.next()); + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + static BlockState stateFromKeyMap(Block block, String keyMap) { + var stateFactory = block.getStateManager(); + var state = block.getDefaultState(); + for (String pair : COMMA_SPLITTER.split(keyMap)) { + Iterator iterator = KEY_VALUE_SPLITTER.split(pair).iterator(); + if (!iterator.hasNext()) continue; + String propertyName = iterator.next(); + var property = stateFactory.getProperty(propertyName); + if (property != null && iterator.hasNext()) { + String value = iterator.next(); + var comparable = property.parse(value).orElse(null); + if (comparable != null) { + state = state.with((Property)property, (Comparable)comparable); + continue; + } + throw new RuntimeException("Unknown value: '" + value + "' for blockstate property: '" + propertyName + "' " + property.getValues()); + } + if (!propertyName.isEmpty()) { + throw new RuntimeException("Unknown blockstate property: '" + propertyName + "'"); + } + } + return state; + } } diff --git a/src/main/java/com/minelittlepony/unicopia/client/gui/UHud.java b/src/main/java/com/minelittlepony/unicopia/client/gui/UHud.java index c731c559..ac25ce7d 100644 --- a/src/main/java/com/minelittlepony/unicopia/client/gui/UHud.java +++ b/src/main/java/com/minelittlepony/unicopia/client/gui/UHud.java @@ -3,6 +3,7 @@ package com.minelittlepony.unicopia.client.gui; import java.util.List; import org.jetbrains.annotations.Nullable; + import com.minelittlepony.unicopia.*; import com.minelittlepony.unicopia.ability.*; import com.minelittlepony.unicopia.ability.magic.spell.effect.CustomisedSpellType; diff --git a/src/main/java/com/minelittlepony/unicopia/client/gui/spellbook/element/DynamicContent.java b/src/main/java/com/minelittlepony/unicopia/client/gui/spellbook/element/DynamicContent.java index cbcd7392..d4d7e8f2 100644 --- a/src/main/java/com/minelittlepony/unicopia/client/gui/spellbook/element/DynamicContent.java +++ b/src/main/java/com/minelittlepony/unicopia/client/gui/spellbook/element/DynamicContent.java @@ -93,6 +93,7 @@ public class DynamicContent implements Content { class Page implements Drawable { private final Text title; private final int level; + private final int color; private final List elements; @@ -103,6 +104,7 @@ public class DynamicContent implements Content { public Page(PacketByteBuf buffer) { title = buffer.readText(); level = buffer.readInt(); + color = buffer.readInt(); elements = buffer.readList(r -> PageElement.read(this, r)); } @@ -132,6 +134,10 @@ public class DynamicContent implements Content { return bounds; } + public int getColor() { + return color == 0 ? MagicText.getColor() : color; + } + public void reset() { compiled = false; } diff --git a/src/main/java/com/minelittlepony/unicopia/client/gui/spellbook/element/PageElement.java b/src/main/java/com/minelittlepony/unicopia/client/gui/spellbook/element/PageElement.java index 6af9020d..8185da9f 100644 --- a/src/main/java/com/minelittlepony/unicopia/client/gui/spellbook/element/PageElement.java +++ b/src/main/java/com/minelittlepony/unicopia/client/gui/spellbook/element/PageElement.java @@ -59,6 +59,7 @@ public interface PageElement extends Drawable { default -> throw new IllegalArgumentException("Unexpected value: " + t); }; })); + case 5 -> new Structure.Builder().fromBuffer(buffer).build(); default -> throw new IllegalArgumentException("Unexpected value: " + type); }; } diff --git a/src/main/java/com/minelittlepony/unicopia/client/gui/spellbook/element/Structure.java b/src/main/java/com/minelittlepony/unicopia/client/gui/spellbook/element/Structure.java new file mode 100644 index 00000000..a29ea8f3 --- /dev/null +++ b/src/main/java/com/minelittlepony/unicopia/client/gui/spellbook/element/Structure.java @@ -0,0 +1,181 @@ +package com.minelittlepony.unicopia.client.gui.spellbook.element; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.Supplier; + +import com.minelittlepony.common.client.gui.IViewRoot; +import com.minelittlepony.common.client.gui.dimension.Bounds; + +import net.minecraft.block.Block; +import net.minecraft.block.BlockState; +import net.minecraft.block.Blocks; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.gui.DrawContext; +import net.minecraft.client.render.DiffuseLighting; +import net.minecraft.client.render.LightmapTextureManager; +import net.minecraft.client.render.OverlayTexture; +import net.minecraft.client.render.VertexConsumerProvider.Immediate; +import net.minecraft.client.util.math.MatrixStack; +import net.minecraft.network.PacketByteBuf; +import net.minecraft.state.property.Properties; +import net.minecraft.util.math.Direction; +import net.minecraft.util.math.MathHelper; +import net.minecraft.util.math.RotationAxis; + +public record Structure(Bounds bounds, List>> states) implements PageElement { + static final BlockState DIAMOND = Blocks.DIAMOND_BLOCK.getDefaultState(); + static final BlockState AIR = Blocks.AIR.getDefaultState(); + static final BlockState OBS = Blocks.OBSIDIAN.getDefaultState(); + static final BlockState SOU = Blocks.SOUL_SAND.getDefaultState(); + public static final Structure CRYSTAL_HEART_ALTAR = new Structure.Builder() + .fill(0, 0, 0, 2, 0, 2, DIAMOND) + .set(1, 1, 1, DIAMOND) + .set(1, 2, 1, Blocks.END_ROD.getDefaultState().with(Properties.FACING, Direction.UP)) + .set(1, 4, 1, Blocks.END_ROD.getDefaultState().with(Properties.FACING, Direction.DOWN)) + .set(1, 5, 1, DIAMOND) + .fill(0, 6, 0, 2, 6, 2, DIAMOND) + .build(); + public static final Structure ALTAR_STRUCTURE = new Structure.Builder() + .fill(0, 0, 0, 8, 0, 8, SOU) + .fill(3, 1, 3, 5, 1, 5, OBS) + .set(4, 1, 4, SOU) + .set(4, 1, 6, Blocks.LODESTONE.getDefaultState()) + .fill(0, 1, 2, 0, 4, 2, OBS).fill(0, 1, 6, 0, 4, 6, OBS) + .fill(2, 1, 0, 2, 4, 0, OBS).fill(6, 1, 0, 6, 4, 0, OBS) + .fill(8, 1, 2, 8, 4, 2, OBS).fill(8, 1, 6, 8, 4, 6, OBS) + .fill(2, 1, 8, 2, 4, 8, OBS).fill(6, 1, 8, 6, 4, 8, OBS) + .build(); + + @Override + public void draw(DrawContext context, int mouseX, int mouseY, IViewRoot container) { + int height = states.size(); + if (height == 0) { + return; + } + int depth = states.get(0).size(); + if (depth == 0) { + return; + } + int width = states.get(0).get(0).size(); + if (width == 0) { + return; + } + MatrixStack matrices = context.getMatrices(); + Immediate immediate = context.getVertexConsumers(); + + MinecraftClient client = MinecraftClient.getInstance(); + float tickDelta = client.player.age + client.getTickDelta(); + float age = tickDelta % 360F; + + matrices.push(); + if (container != null) { + matrices.translate(container.getBounds().width / 2, container.getBounds().height / 2, 100); + float minDimensions = Math.min(container.getBounds().width, container.getBounds().height) - 30; + int minSize = Math.max(height, Math.max(width, depth)) * 16; + float scale = minDimensions / minSize; + matrices.scale(scale, scale, 1); + } + matrices.scale(-16, -16, -16); + matrices.multiply(RotationAxis.POSITIVE_X.rotationDegrees(-20 + MathHelper.sin(tickDelta / 10F) * 2)); + matrices.peek().getPositionMatrix().rotate(RotationAxis.POSITIVE_Y.rotationDegrees(age)); + DiffuseLighting.enableForLevel(matrices.peek().getPositionMatrix()); + + matrices.translate(-width / 2F, -height / 2F, -depth / 2F); + + for (int y = 0; y < height; y++) + for (int x = 0; x < width; x++) + for (int z = 0; z < depth; z++) { + BlockState state = states.get(y).get(z).get(x); + matrices.push(); + matrices.translate(x, y, z); + client.getBlockRenderManager().renderBlockAsEntity(state, matrices, immediate, LightmapTextureManager.MAX_LIGHT_COORDINATE, OverlayTexture.DEFAULT_UV); + matrices.pop(); + } + + matrices.pop(); + } + + public static class Builder { + private int dx = -1; + private int dy = -1; + private int dz = -1; + private final List>> layers = new ArrayList<>(); + + private void resize(int x, int y, int z) { + int ddx = Math.max(dx, x); + int ddy = Math.max(dy, y); + int ddz = Math.max(dz, z); + + if (ddx <= dx && ddy <= dy && ddz <= dz) { + return; + } + + if (ddy > dy) { + dy = ddy; + while (layers.size() <= ddy) { + layers.add(createFilledList(ddz, () -> createFilledList(ddx, () -> AIR))); + } + } + if (ddz > dz || ddx > dx) { + layers.forEach(layer -> { + if (ddz > dz) { + while (layer.size() <= ddz) { + layer.add(createFilledList(ddx, () -> AIR)); + } + } + if (ddx > dx) { + layer.forEach(row -> { + while (row.size() <= ddx) { + row.add(AIR); + } + }); + } + }); + dz = ddz; + dx = ddx; + } + } + + private List createFilledList(int length, Supplier contentSupplier) { + List list = new ArrayList<>(); + while (list.size() <= length) { + list.add(contentSupplier.get()); + } + return list; + } + + public Builder set(int x, int y, int z, BlockState state) { + resize(x, y, z); + layers.get(y).get(z).set(x, state); + return this; + } + + public Builder fill(int minX, int minY, int minZ, int maxX, int maxY, int maxZ, BlockState state) { + resize(maxX, maxY, maxZ); + for (int x = minX; x <= maxX; x++) + for (int y = minY; y <= maxY; y++) + for (int z = minZ; z <= maxZ; z++) + layers.get(y).get(z).set(x, state); + return this; + } + + public Builder fromBuffer(PacketByteBuf buffer) { + + buffer.readCollection(ArrayList::new, buf -> { + byte op = buf.readByte(); + return switch (op) { + case 1 -> set(buf.readInt(), buf.readInt(), buf.readInt(), Block.getStateFromRawId(buf.readInt())); + case 2 -> fill(buf.readInt(), buf.readInt(), buf.readInt(), buf.readInt(), buf.readInt(), buf.readInt(), Block.getStateFromRawId(buf.readInt())); + default -> this; + }; + }); + + return this; + } + + public Structure build() { + return new Structure(Bounds.empty(), layers); + } + } +} diff --git a/src/main/java/com/minelittlepony/unicopia/client/gui/spellbook/element/TextBlock.java b/src/main/java/com/minelittlepony/unicopia/client/gui/spellbook/element/TextBlock.java index 422996d7..165b75cf 100644 --- a/src/main/java/com/minelittlepony/unicopia/client/gui/spellbook/element/TextBlock.java +++ b/src/main/java/com/minelittlepony/unicopia/client/gui/spellbook/element/TextBlock.java @@ -6,7 +6,6 @@ import java.util.function.Supplier; import com.minelittlepony.common.client.gui.IViewRoot; import com.minelittlepony.common.client.gui.dimension.Bounds; -import com.minelittlepony.unicopia.client.gui.MagicText; import com.minelittlepony.unicopia.client.gui.ParagraphWrappingVisitor; import com.minelittlepony.unicopia.container.SpellbookChapterLoader.Flow; import com.minelittlepony.unicopia.entity.player.Pony; @@ -53,7 +52,7 @@ class TextBlock implements PageElement { MatrixStack matrices = context.getMatrices(); matrices.push(); wrappedText.forEach(line -> { - context.drawText(font, needsMoreXp ? line.text().copy().formatted(Formatting.OBFUSCATED) : line.text().copy(), line.x(), 0, MagicText.getColor(), false); + context.drawText(font, needsMoreXp ? line.text().copy().formatted(Formatting.OBFUSCATED) : line.text().copy(), line.x(), 0, page.getColor(), false); matrices.translate(0, font.fontHeight, 0); }); matrices.pop(); diff --git a/src/main/java/com/minelittlepony/unicopia/container/SpellbookChapterLoader.java b/src/main/java/com/minelittlepony/unicopia/container/SpellbookChapterLoader.java index d7c7b5dc..fddeb027 100644 --- a/src/main/java/com/minelittlepony/unicopia/container/SpellbookChapterLoader.java +++ b/src/main/java/com/minelittlepony/unicopia/container/SpellbookChapterLoader.java @@ -13,18 +13,21 @@ import com.minelittlepony.unicopia.Debug; import com.minelittlepony.unicopia.Unicopia; import com.minelittlepony.unicopia.ability.magic.spell.crafting.IngredientWithSpell; import com.minelittlepony.unicopia.ability.magic.spell.trait.Trait; +import com.minelittlepony.unicopia.block.state.StateUtil; import com.minelittlepony.unicopia.client.gui.spellbook.SpellbookChapterList.*; import com.minelittlepony.unicopia.network.Channel; import com.minelittlepony.unicopia.network.MsgServerResources; import com.minelittlepony.unicopia.util.Resources; import com.mojang.logging.LogUtils; - import net.fabricmc.fabric.api.resource.IdentifiableResourceReloadListener; +import net.minecraft.block.Block; +import net.minecraft.block.BlockState; import net.minecraft.network.PacketByteBuf; import net.minecraft.resource.JsonDataLoader; import net.minecraft.resource.ResourceManager; import net.minecraft.server.MinecraftServer; import net.minecraft.text.Text; +import net.minecraft.text.TextColor; import net.minecraft.util.*; import net.minecraft.util.profiler.Profiler; @@ -128,9 +131,10 @@ public class SpellbookChapterLoader extends JsonDataLoader implements Identifiab private record Page ( Text title, int level, + int color, List elements ) { - private static final Page EMPTY = new Page(Text.empty(), 0, List.of()); + private static final Page EMPTY = new Page(Text.empty(), 0, 0, List.of()); public static Page of(JsonElement json) { return json.isJsonObject() && json.getAsJsonObject().keySet().isEmpty() ? EMPTY : new Page(json); @@ -144,6 +148,7 @@ public class SpellbookChapterLoader extends JsonDataLoader implements Identifiab this( readText(json.get("title")), JsonHelper.getInt(json, "level", 0), + Optional.ofNullable(TextColor.parse(JsonHelper.getString(json, "color", ""))).map(TextColor::getRgb).orElse(0), new ArrayList<>() ); JsonHelper.getArray(json, "elements", new JsonArray()).forEach(element -> { @@ -154,6 +159,7 @@ public class SpellbookChapterLoader extends JsonDataLoader implements Identifiab public void toBuffer(PacketByteBuf buffer) { buffer.writeText(title); buffer.writeInt(level); + buffer.writeInt(color); buffer.writeCollection(elements, Element::write); } @@ -208,6 +214,59 @@ public class SpellbookChapterLoader extends JsonDataLoader implements Identifiab } } + record Structure(List commands) implements Element { + static Element loadCommand(JsonObject json) { + + if (json.has("pos")) { + var pos = JsonHelper.getArray(json, "pos"); + return new Set( + pos.get(0).getAsInt(), pos.get(1).getAsInt(), pos.get(2).getAsInt(), + StateUtil.stateFromString(json.get("state").getAsString()) + ); + } + + var min = JsonHelper.getArray(json, "min"); + var max = JsonHelper.getArray(json, "max"); + return new Fill( + min.get(0).getAsInt(), min.get(1).getAsInt(), min.get(2).getAsInt(), + max.get(0).getAsInt(), max.get(1).getAsInt(), max.get(2).getAsInt(), + StateUtil.stateFromString(json.get("state").getAsString()) + ); + } + + @Override + public void toBuffer(PacketByteBuf buffer) { + buffer.writeByte(5); + buffer.writeCollection(commands, (b, c) -> c.toBuffer(b)); + } + + record Set(int x, int y, int z, BlockState state) implements Element { + @Override + public void toBuffer(PacketByteBuf buffer) { + buffer.writeByte(1); + buffer.writeInt(x); + buffer.writeInt(y); + buffer.writeInt(z); + buffer.writeInt(Block.getRawIdFromState(state)); + } + + } + record Fill(int x1, int y1, int z1, int x2, int y2, int z2, BlockState state) implements Element { + @Override + public void toBuffer(PacketByteBuf buffer) { + buffer.writeByte(2); + buffer.writeInt(x1); + buffer.writeInt(y1); + buffer.writeInt(z1); + buffer.writeInt(x2); + buffer.writeInt(y2); + buffer.writeInt(z2); + buffer.writeInt(Block.getRawIdFromState(state)); + } + + } + } + record Ingredients(List entries) implements Element { static Element loadIngredient(JsonObject json) { int count = JsonHelper.getInt(json, "count", 1); @@ -263,6 +322,14 @@ public class SpellbookChapterLoader extends JsonDataLoader implements Identifiab .toList() ); } + + if (el.has("structure")) { + return new Structure(JsonHelper.getArray(el, "structure").asList().stream() + .map(JsonElement::getAsJsonObject) + .map(Structure::loadCommand) + .toList() + ); + } } return new TextBlock(readText(json)); diff --git a/src/main/resources/assets/unicopia/lang/en_us.json b/src/main/resources/assets/unicopia/lang/en_us.json index 4e5ba421..3ed7b0ab 100644 --- a/src/main/resources/assets/unicopia/lang/en_us.json +++ b/src/main/resources/assets/unicopia/lang/en_us.json @@ -1116,6 +1116,10 @@ "gui.unicopia.spellbook.chapter.artefacts.torn_page.2.body": "§kAasa sasa fwefsd q43rgfd wqklmsdfl as, klasn.§r", "gui.unicopia.spellbook.chapter.artefacts.torn_page.3.body": "Building Materials:", "gui.unicopia.spellbook.chapter.artefacts.crystal_podium.title": "Crystal Podium", + "gui.unicopia.spellbook.chapter.artefacts.altar.title": "Altar", + "gui.unicopia.spellbook.chapter.artefacts.altar.1.body": "An an§kc§rient altar con§ktr§ructed by an early t§kr§ribe of §kunicorn§rs. It's thought that these were used to perform r§kituals§r sum§kmoni§rng upon §keven o§rlder mag§kics§r.", + "gui.unicopia.spellbook.chapter.artefacts.altar.2.body": "Not much is §kkn§rown about these my§ks§rterious structures, and they hold many secrets even to this day.", + "gui.unicopia.spellbook.chapter.artefacts.altar.3.body": "U§ks§re the §ka§rltar", "gui.unicopia.spellbook.chapter.artefacts.dragon_breath_scroll.2.body": "It's, um a scroll that you write somepony's name on it and you hold it in one hoof and something in the other hoof and, like, um it goes whooosh and the item is sent to that pony.", "gui.unicopia.spellbook.chapter.artefacts.dragon_breath_scroll.title": "2nd Hoof '12", "gui.unicopia.spellbook.chapter.artefacts.dragon_breath_scroll.3.body": "P.S. Uncle Starswirly is a dunderhead.", @@ -1124,6 +1128,9 @@ "gui.unicopia.spellbook.chapter.artefacts.friendship_bracelet.title": "13th Mare '12", "gui.unicopia.spellbook.chapter.artefacts.friendship_bracelet.3.body": "Anyone wearing a bangle you have signed will be able to benefit from the positive effects of your spells, or will be allowed through protection and shield spells.", "gui.unicopia.spellbook.chapter.artefacts.friendship_bracelet.4.body": "Mana costs are also shared equally between all nearby members.", + "gui.unicopia.spellbook.chapter.artefacts.spectral_clock.title": "14th Mare '12", + "gui.unicopia.spellbook.chapter.artefacts.spectral_clock.1.body": "Not so much an artefact as a strange trinket. Luna happened to bring this home from the market last week, and though at first glance it may seem to be an ordinary broken clock what I've found is far stranger.", + "gui.unicopia.spellbook.chapter.artefacts.spectral_clock.2.body": "This clock doesn't tell the time. Well, it does, but not directly. Rather it seems to be following the cycles of some of the plants in the surrounding forest.", "gui.unicopia.spellbook.chapter.artefacts.pegasus_amulet.1.body": "Commander Hurricane informed me of this, though I've found little texts to back up his claims.", "gui.unicopia.spellbook.chapter.artefacts.pegasus_amulet.2.body": "The Pegasus Amulet is claimed to grant the wearer temporary flight, like a pegasus.", "gui.unicopia.spellbook.chapter.artefacts.pegasus_amulet.title": "21st Trot '12", diff --git a/src/main/resources/data/unicopia/spellbook/chapters/crystal_heart.json b/src/main/resources/data/unicopia/spellbook/chapters/crystal_heart.json index 602110cb..87bfca53 100644 --- a/src/main/resources/data/unicopia/spellbook/chapters/crystal_heart.json +++ b/src/main/resources/data/unicopia/spellbook/chapters/crystal_heart.json @@ -58,17 +58,77 @@ "title": "gui.unicopia.spellbook.chapter.artefacts.crystal_podium.title", "level": 0, "elements": [ - { "x": 40, "item": { "item": "minecraft:diamond_block" } }, - { "x": 60, "y": -17, "item": { "item": "minecraft:diamond_block" } }, - { "x": 80, "y": -34, "item": { "item": "minecraft:diamond_block" } }, - { "x": 60, "y": -34, "item": { "item": "minecraft:diamond_block" } }, - { "x": 60, "y": -34, "item": { "item": "minecraft:end_rod" } }, - { "x": 60, "y": -34, "item": { "item": "unicopia:crystal_heart" } }, - { "x": 60, "y": -34, "item": { "item": "minecraft:end_rod" } }, - { "x": 60, "y": -34, "item": { "item": "minecraft:diamond_block" } }, - { "x": 40, "y": -34, "item": { "item": "minecraft:diamond_block" } }, - { "x": 60, "y": -51, "item": { "item": "minecraft:diamond_block" } }, - { "x": 80, "y": -68, "item": { "item": "minecraft:diamond_block" } } + { + "structure": [ + { "state": "minecraft:diamond_block", "min": [0,0,0], "max": [2,0,2] }, + { "state": "minecraft:diamond_block", "pos": [1,1,1] }, + { "state": "minecraft:end_rod[facing=up]", "pos": [1,2,1] }, + { "state": "minecraft:end_rod[facing=down]", "pos": [1,4,1] }, + { "state": "minecraft:diamond_block", "pos": [1,5,1] }, + { "state": "minecraft:diamond_block", "min": [0,6,0], "max": [2,6,2] } + ] + }, + { "x": 60, "y": -34, "item": { "item": "unicopia:crystal_heart" } } + ] + }, + { + "title": "gui.unicopia.spellbook.chapter.artefacts.altar.title", + "level": 0, + "elements": [ + "gui.unicopia.spellbook.chapter.artefacts.altar.1.body", + "gui.unicopia.spellbook.chapter.artefacts.altar.2.body", + { + "ingredients": [ + { "count": 40, "item": "minecraft:obsidian" }, + { "count": 1, "item": "minecraft:soul_sand" }, + { "count": 1, "item": "minecraft:lodestone" }, + { "count": 1, "item": "unicopia:spellbook" } + ] + } + ] + }, + { + "title": "gui.unicopia.spellbook.chapter.artefacts.altar.title", + "level": 0, + "elements": [ + { + "structure": [ + { "state": "minecraft:soul_sand", "min": [0,0,0], "max": [8,0,8] }, + { "state": "minecraft:obsidian", "min": [3,1,3], "max": [5,1,5] }, + { "state": "minecraft:soul_sand", "pos": [4,1,4] }, + { "state": "minecraft:lodestone", "pos": [4,1,6] }, + { "state": "minecraft:obsidian", "min": [0,1,2], "max": [0,4,2] }, + { "state": "minecraft:obsidian", "min": [0,1,6], "max": [0,4,6] }, + { "state": "minecraft:obsidian", "min": [2,1,0], "max": [2,4,0] }, + { "state": "minecraft:obsidian", "min": [6,1,0], "max": [6,4,0] }, + { "state": "minecraft:obsidian", "min": [8,1,2], "max": [8,4,2] }, + { "state": "minecraft:obsidian", "min": [8,1,6], "max": [8,4,6] }, + { "state": "minecraft:obsidian", "min": [2,1,8], "max": [2,4,8] }, + { "state": "minecraft:obsidian", "min": [6,1,8], "max": [6,4,8] } + ] + } + ] + }, + { + "title": "item.unicopia.spectral_clock", + "level": 0, + "elements": [ + { "item": { "item": "unicopia:spectral_clock" } }, + "gui.unicopia.spellbook.chapter.artefacts.status.unconfirmed", + "gui.unicopia.spellbook.chapter.artefacts.spectral_clock.1.body", + { + "translate": "gui.unicopia.spellbook.chapter.artefacts.altar.3.body", + "color": "red" + } + ] + }, + { + "title": "gui.unicopia.spellbook.chapter.artefacts.spectral_clock.title", + "level": 0, + "elements": [ + "gui.unicopia.spellbook.chapter.artefacts.spectral_clock.2.body", + "gui.unicopia.spellbook.author1.sign_off", + "gui.unicopia.spellbook.author1.name" ] }, {