No files matched your search
@@ -13,6 +13,10 @@ import net.neoforged.fml.event.lifecycle.FMLClientSetupEvent;
|
||||
import net.neoforged.neoforge.common.NeoForge;
|
||||
import net.neoforged.neoforge.client.event.EntityRenderersEvent;
|
||||
import net.neoforged.neoforge.client.event.RegisterMenuScreensEvent;
|
||||
import net.neoforged.neoforge.client.event.RegisterClientReloadListenersEvent;
|
||||
import net.neoforged.neoforge.client.extensions.common.IClientItemExtensions;
|
||||
import net.neoforged.neoforge.client.extensions.common.RegisterClientExtensionsEvent;
|
||||
import net.minecraft.client.renderer.BlockEntityWithoutLevelRenderer;
|
||||
import net.dnorberg.rivencraft.client.screen.NotebookScreen;
|
||||
import net.dnorberg.rivencraft.client.screen.WritingDeskScreen;
|
||||
import net.dnorberg.rivencraft.client.screen.BookBinderScreen;
|
||||
@@ -38,6 +42,24 @@ public final class ClientEvents {
|
||||
event.register(ModMenus.INK_MIXER.get(), InkMixerScreen::new);
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public static void registerPageItemRenderer(RegisterClientExtensionsEvent event) {
|
||||
event.registerItem(new IClientItemExtensions() {
|
||||
private final SymbolPageItemRenderer renderer = new SymbolPageItemRenderer();
|
||||
|
||||
@Override
|
||||
public BlockEntityWithoutLevelRenderer getCustomRenderer() {
|
||||
return renderer;
|
||||
}
|
||||
}, ModItems.PAGE.get());
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public static void registerPageTextureReload(RegisterClientReloadListenersEvent event) {
|
||||
event.registerReloadListener((net.minecraft.server.packs.resources.ResourceManagerReloadListener)
|
||||
manager -> SymbolPageTextures.clear());
|
||||
}
|
||||
|
||||
@SubscribeEvent
|
||||
public static void clientSetup(FMLClientSetupEvent event) {
|
||||
event.enqueueWork(() -> ItemProperties.register(
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
package net.dnorberg.rivencraft.client;
|
||||
|
||||
import com.mojang.blaze3d.vertex.PoseStack;
|
||||
import com.mojang.blaze3d.vertex.VertexConsumer;
|
||||
import net.dnorberg.rivencraft.Rivencraft;
|
||||
import net.dnorberg.rivencraft.item.PageItem;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.renderer.BlockEntityWithoutLevelRenderer;
|
||||
import net.minecraft.client.renderer.MultiBufferSource;
|
||||
import net.minecraft.client.renderer.RenderType;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.world.item.ItemDisplayContext;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
|
||||
/** Flat page model whose symbol artwork is resolved from the stack at render time. */
|
||||
public final class SymbolPageItemRenderer extends BlockEntityWithoutLevelRenderer {
|
||||
public SymbolPageItemRenderer() {
|
||||
super(Minecraft.getInstance().getBlockEntityRenderDispatcher(), Minecraft.getInstance().getEntityModels());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void renderByItem(ItemStack stack, ItemDisplayContext context, PoseStack pose,
|
||||
MultiBufferSource buffers, int light, int overlay) {
|
||||
ResourceLocation texture = switch (PageItem.content(stack).kind()) {
|
||||
case BLANK -> texture("page");
|
||||
case LINK_PANEL -> texture("page_link_panel");
|
||||
case UNRESOLVED_SYMBOL -> texture("page_unresolved");
|
||||
case SYMBOL -> SymbolPageTextures.forItem(stack);
|
||||
};
|
||||
VertexConsumer vertices = buffers.getBuffer(RenderType.entityCutoutNoCull(texture));
|
||||
var matrix = pose.last().pose();
|
||||
vertex(vertices, matrix, 0, 0, 0, 1, light, overlay);
|
||||
vertex(vertices, matrix, 1, 0, 1, 1, light, overlay);
|
||||
vertex(vertices, matrix, 1, 1, 1, 0, light, overlay);
|
||||
vertex(vertices, matrix, 0, 1, 0, 0, light, overlay);
|
||||
}
|
||||
|
||||
private static void vertex(VertexConsumer vertices, org.joml.Matrix4f matrix,
|
||||
float x, float y, float u, float v, int light, int overlay) {
|
||||
vertices.addVertex(matrix, x, y, 0.5F).setColor(255, 255, 255, 255)
|
||||
.setUv(u, v).setOverlay(overlay).setLight(light).setNormal(0, 0, 1);
|
||||
}
|
||||
|
||||
private static ResourceLocation texture(String name) {
|
||||
return ResourceLocation.fromNamespaceAndPath(Rivencraft.MOD_ID, "textures/item/" + name + ".png");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,240 @@
|
||||
package net.dnorberg.rivencraft.client;
|
||||
|
||||
import com.mojang.blaze3d.platform.NativeImage;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Random;
|
||||
import net.dnorberg.rivencraft.Rivencraft;
|
||||
import net.dnorberg.rivencraft.item.PageItem;
|
||||
import net.dnorberg.rivencraft.registry.ModDataComponents;
|
||||
import net.dnorberg.rivencraft.writing.PageContent;
|
||||
import net.dnorberg.rivencraft.writing.SymbolDisplayMetadata;
|
||||
import net.dnorberg.rivencraft.writing.SymbolPoems;
|
||||
import net.dnorberg.rivencraft.writing.SymbolNumerals;
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.GuiGraphics;
|
||||
import net.minecraft.client.renderer.texture.DynamicTexture;
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
import net.minecraft.server.packs.resources.ResourceManager;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
|
||||
/** Client-only, bounded cache of Legacy-style page compositions; no per-symbol PNGs. */
|
||||
public final class SymbolPageTextures {
|
||||
private static final int SIZE = 128;
|
||||
private static final int GLYPH = 51;
|
||||
// A 16 px item icon displays a 13 px symbol: three pixels narrower overall.
|
||||
private static final int ITEM_SYMBOL_SIZE = 13;
|
||||
private static final int ICON_SIZE = 16;
|
||||
private static final int CACHE_LIMIT = 256;
|
||||
private static final ResourceLocation BASE = texture("item/page");
|
||||
private static final ResourceLocation COMPONENTS = texture("gui/symbolcomponents");
|
||||
private static final ResourceLocation FALLBACK = texture("item/page_symbol");
|
||||
private static final Map<String, ResourceLocation> CACHE = new LinkedHashMap<>(16, .75F, true);
|
||||
private static final Map<String, int[]> WORD_CACHE = new LinkedHashMap<>(16, .75F, true);
|
||||
private static NativeImage base;
|
||||
private static NativeImage components;
|
||||
private static int nextId;
|
||||
|
||||
// WordData from Mystcraft Legacy 0.13.7.06. Other words get a stable generated glyph.
|
||||
private static final Map<String, int[]> WORDS = Map.ofEntries(
|
||||
Map.entry("Terrain", new int[] {6,10,12,13,16,19,24,25,27}),
|
||||
Map.entry("Form", new int[] {4,6,8,9,10,11,13,14,16,23}),
|
||||
Map.entry("Tradition", new int[] {4,6,7,8,9,14,17,18,20}),
|
||||
Map.entry("Flow", new int[] {4,7,12,13,17,18}),
|
||||
Map.entry("Constraint", new int[] {5,6,9,11,13,20,22,23}),
|
||||
Map.entry("Nature", new int[] {5,6,8,10,11,12,15,16,17,22}),
|
||||
Map.entry("Sustain", new int[] {6,16,19,20,21,23}),
|
||||
Map.entry("Ethereal", new int[] {4,6,8,13,14,16,18,21,23}),
|
||||
Map.entry("Dynamic", new int[] {4,5,6,12,13,17,18,22,23}),
|
||||
Map.entry("Cycle", new int[] {4,5,7,16,17,18}),
|
||||
Map.entry("Balance", new int[] {4,6,9,10,16,19}),
|
||||
Map.entry("Transform", new int[] {4,5,6,8,11,14,18,20,21}),
|
||||
Map.entry("Time", new int[] {4,5,7,12,13,19,20,21,22,23}),
|
||||
Map.entry("System", new int[] {10,12,14,15,16,20,21,22}),
|
||||
Map.entry("Void", new int[] {4,5,6,16,17,18,23})
|
||||
);
|
||||
|
||||
private SymbolPageTextures() {}
|
||||
|
||||
/** Transparent full-size artwork for GUI thumbnails drawn over their own page background. */
|
||||
public static ResourceLocation forPage(ItemStack stack) {
|
||||
return forPage(stack, false);
|
||||
}
|
||||
|
||||
/** Full page texture with artwork reduced by three pixels at 16 px item-icon scale. */
|
||||
public static ResourceLocation forItem(ItemStack stack) {
|
||||
return forPage(stack, true);
|
||||
}
|
||||
|
||||
private static ResourceLocation forPage(ItemStack stack, boolean item) {
|
||||
PageContent content = PageItem.content(stack);
|
||||
if (content.kind() == PageContent.Kind.UNRESOLVED_SYMBOL) return texture("item/page_unresolved");
|
||||
if (content.kind() != PageContent.Kind.SYMBOL) return FALLBACK;
|
||||
SymbolDisplayMetadata metadata = stack.get(ModDataComponents.PAGE_SYMBOL_DISPLAY.get());
|
||||
ResourceLocation id = content.symbol().orElseThrow().id();
|
||||
return forSymbol(id, metadata == null ? "" : metadata.poem(), item);
|
||||
}
|
||||
|
||||
private static ResourceLocation forSymbol(ResourceLocation id, String poem, boolean item) {
|
||||
if (poem.length() > 96 || !poem.matches("[A-Za-z]+(?: [A-Za-z]+){3}")) poem = "";
|
||||
if (poem.isEmpty()) poem = SymbolPoems.builtIn(id);
|
||||
String key = id + "|" + poem + (item ? "|item" : "|thumbnail");
|
||||
ResourceLocation cached = CACHE.get(key);
|
||||
if (cached != null) return cached;
|
||||
try {
|
||||
loadSources(Minecraft.getInstance().getResourceManager());
|
||||
NativeImage image = compose(id, poem, item);
|
||||
ResourceLocation location = ResourceLocation.fromNamespaceAndPath(
|
||||
Rivencraft.MOD_ID, "generated/page_" + nextId++);
|
||||
Minecraft.getInstance().getTextureManager().register(location, new DynamicTexture(image));
|
||||
CACHE.put(key, location);
|
||||
if (CACHE.size() > CACHE_LIMIT) {
|
||||
String eldest = CACHE.keySet().iterator().next();
|
||||
Minecraft.getInstance().getTextureManager().release(CACHE.remove(eldest));
|
||||
}
|
||||
return location;
|
||||
} catch (IOException exception) {
|
||||
Rivencraft.LOGGER.error("Could not compose symbol page {}", id, exception);
|
||||
return FALLBACK;
|
||||
}
|
||||
}
|
||||
|
||||
public static void clear() {
|
||||
var textures = Minecraft.getInstance().getTextureManager();
|
||||
CACHE.values().forEach(textures::release);
|
||||
CACHE.clear();
|
||||
WORD_CACHE.clear();
|
||||
if (base != null) base.close();
|
||||
if (components != null) components.close();
|
||||
base = null;
|
||||
components = null;
|
||||
}
|
||||
|
||||
private static void loadSources(ResourceManager manager) throws IOException {
|
||||
if (base != null && components != null) return;
|
||||
try (InputStream page = manager.getResourceOrThrow(BASE).open();
|
||||
InputStream sheet = manager.getResourceOrThrow(COMPONENTS).open()) {
|
||||
base = NativeImage.read(page);
|
||||
components = NativeImage.read(sheet);
|
||||
}
|
||||
}
|
||||
|
||||
private static NativeImage compose(ResourceLocation id, String poem, boolean item) {
|
||||
// GUI variants have no parchment layer, so their untouched pixels must be transparent.
|
||||
NativeImage image = new NativeImage(SIZE, SIZE, true);
|
||||
if (item) {
|
||||
for (int y = 0; y < SIZE; y++) for (int x = 0; x < SIZE; x++) {
|
||||
image.setPixelRGBA(x, y, base.getPixelRGBA(x * base.getWidth() / SIZE,
|
||||
y * base.getHeight() / SIZE));
|
||||
}
|
||||
}
|
||||
String[] words = poem.isBlank() ? generatedWords(id) : poem.split(" ");
|
||||
int[] offsetsX = {38, 77, 38, 0};
|
||||
int[] offsetsY = {0, 38, 77, 38};
|
||||
int glyphSize = item ? GLYPH * ITEM_SYMBOL_SIZE / ICON_SIZE : GLYPH;
|
||||
for (int word = 0; word < 4; word++) {
|
||||
int originX = item ? scaleForItem(offsetsX[word]) : offsetsX[word];
|
||||
int originY = item ? scaleForItem(offsetsY[word]) : offsetsY[word];
|
||||
for (int component : glyphs(words[word])) {
|
||||
for (int y = 0; y < glyphSize; y++) for (int x = 0; x < glyphSize; x++) {
|
||||
int pixel = components.getPixelRGBA((component % 8) * 64 + x * 64 / glyphSize,
|
||||
(component / 8) * 64 + y * 64 / glyphSize);
|
||||
int alpha = pixel >>> 24;
|
||||
if (alpha != 0) image.blendPixel(originX + x, originY + y,
|
||||
(alpha << 24) | 0x001A212B);
|
||||
}
|
||||
}
|
||||
}
|
||||
return image;
|
||||
}
|
||||
|
||||
private static int scaleForItem(int coordinate) {
|
||||
return SIZE / 2 + Math.round((coordinate - SIZE / 2F) * ITEM_SYMBOL_SIZE / ICON_SIZE);
|
||||
}
|
||||
|
||||
private static String[] generatedWords(ResourceLocation id) {
|
||||
String stem = id.toString();
|
||||
return new String[] {stem + 'a', stem + 'b', stem + 'c', stem + 'd'};
|
||||
}
|
||||
|
||||
private static int[] glyphs(String word) {
|
||||
int[] known = WORDS.get(word);
|
||||
if (known != null) return known;
|
||||
String key = word.toLowerCase(Locale.ROOT);
|
||||
int[] cached = WORD_CACHE.get(key);
|
||||
if (cached != null) return cached;
|
||||
Random random = new Random(key.hashCode());
|
||||
int[] components = new int[random.nextInt(10) + 3];
|
||||
for (int index = 0; index < components.length; index++) components[index] = random.nextInt(20) + 4;
|
||||
WORD_CACHE.put(key, components);
|
||||
if (WORD_CACHE.size() > CACHE_LIMIT) WORD_CACHE.remove(WORD_CACHE.keySet().iterator().next());
|
||||
return components;
|
||||
}
|
||||
|
||||
/** Draw a word as a layered Legacy glyph, or a number with Legacy's number components. */
|
||||
public static void drawWordGlyph(GuiGraphics graphics, String word, int x, int y, int size) {
|
||||
if (word.matches("[0-9]+")) {
|
||||
try {
|
||||
drawNumeral(graphics, Integer.parseInt(word), x, y, size);
|
||||
return;
|
||||
} catch (NumberFormatException ignored) {
|
||||
// Oversized numerals still receive a stable word glyph.
|
||||
}
|
||||
}
|
||||
graphics.setColor(0.13F, 0.10F, 0.08F, 1F);
|
||||
for (int component : glyphs(word)) drawComponent(graphics, component, x, y, size);
|
||||
graphics.setColor(1F, 1F, 1F, 1F);
|
||||
}
|
||||
|
||||
/** Legacy's single-number marks; numbers above 24 use positional base 25. */
|
||||
public static void drawNumeral(GuiGraphics graphics, int number, int x, int y, int size) {
|
||||
int[] digits = SymbolNumerals.digitsFor(number);
|
||||
int digitSize = digits.length == 1 ? size : Math.max(8, size * 3 / 4);
|
||||
graphics.setColor(0.13F, 0.10F, 0.08F, 1F);
|
||||
for (int digit : digits) {
|
||||
for (int component : SymbolNumerals.componentsForDigit(digit))
|
||||
drawComponent(graphics, component, x, y + (size - digitSize) / 2, digitSize);
|
||||
x += digitSize + 1;
|
||||
}
|
||||
graphics.setColor(1F, 1F, 1F, 1F);
|
||||
}
|
||||
|
||||
/** Render translated prose as a sequence of word glyphs within a fixed-width book page. */
|
||||
public static int drawGlyphText(GuiGraphics graphics, String text, int x, int y, int maxWidth) {
|
||||
int cursor = x;
|
||||
boolean drew = false;
|
||||
for (String word : text.split("[^\\p{L}\\p{N}]+")) {
|
||||
if (word.isEmpty()) continue;
|
||||
int width = word.matches("[0-9]+") ? numeralWidth(word, 12) : 12;
|
||||
if (cursor != x && cursor + width > x + maxWidth) {
|
||||
cursor = x;
|
||||
y += 13;
|
||||
}
|
||||
drawWordGlyph(graphics, word, cursor, y, 12);
|
||||
cursor += width + 3;
|
||||
drew = true;
|
||||
}
|
||||
return y + (drew ? 13 : 0);
|
||||
}
|
||||
|
||||
private static int numeralWidth(String word, int size) {
|
||||
try {
|
||||
int count = SymbolNumerals.digitsFor(Integer.parseInt(word)).length;
|
||||
return count == 1 ? size : count * (size * 3 / 4 + 1) - 1;
|
||||
} catch (NumberFormatException ignored) {
|
||||
return size;
|
||||
}
|
||||
}
|
||||
|
||||
private static void drawComponent(GuiGraphics graphics, int component, int x, int y, int size) {
|
||||
graphics.blit(COMPONENTS, x, y, size, size,
|
||||
(component % 8) * 64, (component / 8) * 64, 64, 64, 512, 512);
|
||||
}
|
||||
|
||||
private static ResourceLocation texture(String path) {
|
||||
return ResourceLocation.fromNamespaceAndPath(Rivencraft.MOD_ID, "textures/" + path + ".png");
|
||||
}
|
||||
}
|
||||
@@ -146,7 +146,7 @@ public final class BookBinderScreen extends AbstractContainerScreen<BookBinderMe
|
||||
case BLANK -> { }
|
||||
case LINK_PANEL -> graphics.fill(x + 5, y + 6, x + 22, y + 17, 0xFF000000);
|
||||
case SYMBOL, UNRESOLVED_SYMBOL -> graphics.blit(NotebookScreen.symbolTexture(page),
|
||||
x + 1, y + 5, 24, 24, 16, 16, 96, 96, 128, 128);
|
||||
x + 1, y + 5, 24, 24, 0, 0, 128, 128, 128, 128);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import java.util.Locale;
|
||||
import net.dnorberg.rivencraft.book.AgeReference;
|
||||
import net.dnorberg.rivencraft.book.AgeTraits;
|
||||
import net.dnorberg.rivencraft.client.ClientBookExploreOverlay;
|
||||
import net.dnorberg.rivencraft.client.SymbolPageTextures;
|
||||
import net.dnorberg.rivencraft.network.UseDescriptiveBookPayload;
|
||||
import net.dnorberg.rivencraft.network.BindDescriptiveBookPayload;
|
||||
import net.dnorberg.rivencraft.network.ModNetworking;
|
||||
@@ -37,7 +38,6 @@ import org.jetbrains.annotations.Nullable;
|
||||
public final class DescriptiveBookScreen extends Screen implements ExplorableBookScreen {
|
||||
private static final int BOOK_TEXT_COLOR = 0xFF3B281B;
|
||||
private static final int UNRESOLVED_TEXT_COLOR = 0xFF9B302C;
|
||||
private static final ResourceLocation ENCHANTMENT_FONT = ResourceLocation.withDefaultNamespace("alt");
|
||||
private static final int BOOK_WIDTH = 327;
|
||||
private static final int BOOK_HEIGHT = 199;
|
||||
private static final int LINK_PANEL_X = 173;
|
||||
@@ -211,7 +211,7 @@ public final class DescriptiveBookScreen extends Screen implements ExplorableBoo
|
||||
Component profileName = generationProfile.getNamespace().equals("rivencraft")
|
||||
? Component.translatable("generator.rivencraft." + generationProfile.getPath())
|
||||
: Component.literal(generationProfile.toString());
|
||||
detailsY = drawWrappedWithoutShadow(graphics,
|
||||
detailsY = drawDescription(graphics,
|
||||
Component.translatable("screen.rivencraft.age.generator", profileName),
|
||||
x, detailsY, 110) + 2;
|
||||
}
|
||||
@@ -415,10 +415,16 @@ public final class DescriptiveBookScreen extends Screen implements ExplorableBoo
|
||||
}
|
||||
|
||||
private int drawTrait(GuiGraphics graphics, String key, Component value, int x, int y) {
|
||||
return drawWrappedWithoutShadow(graphics,
|
||||
return drawDescription(graphics,
|
||||
Component.translatable("screen.rivencraft.age.trait." + key, value), x, y, 110) + 2;
|
||||
}
|
||||
|
||||
private int drawDescription(GuiGraphics graphics, Component text, int x, int y, int maxWidth) {
|
||||
return pageFree && age != null
|
||||
? SymbolPageTextures.drawGlyphText(graphics, text.getString(), x, y, maxWidth)
|
||||
: drawWrappedWithoutShadow(graphics, text, x, y, maxWidth);
|
||||
}
|
||||
|
||||
private static Component traitName(String group, Enum<?> value) {
|
||||
return Component.translatable("trait.rivencraft." + group + "."
|
||||
+ value.name().toLowerCase(Locale.ROOT));
|
||||
@@ -429,9 +435,7 @@ public final class DescriptiveBookScreen extends Screen implements ExplorableBoo
|
||||
}
|
||||
|
||||
private int drawWrappedWithoutShadow(GuiGraphics graphics, Component text, int x, int y, int maxWidth, int color) {
|
||||
Component displayed = pageFree && age != null && pageIndex == 0
|
||||
? text.copy().withStyle(style -> style.withFont(ENCHANTMENT_FONT)) : text;
|
||||
for (FormattedCharSequence line : font.split(displayed, maxWidth)) {
|
||||
for (FormattedCharSequence line : font.split(text, maxWidth)) {
|
||||
graphics.drawString(font, line, x, y, color, false);
|
||||
y += font.lineHeight;
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package net.dnorberg.rivencraft.client.screen;
|
||||
import java.util.Locale;
|
||||
import net.dnorberg.rivencraft.Rivencraft;
|
||||
import net.dnorberg.rivencraft.item.PageItem;
|
||||
import net.dnorberg.rivencraft.client.SymbolPageTextures;
|
||||
import net.dnorberg.rivencraft.menu.NotebookMenu;
|
||||
import net.dnorberg.rivencraft.registry.ModItems;
|
||||
import net.minecraft.client.gui.GuiGraphics;
|
||||
@@ -26,14 +27,6 @@ public final class NotebookScreen extends AbstractContainerScreen<NotebookMenu>
|
||||
private static final int PAGE_STEP_Y = 41;
|
||||
private static final int PAGE_COLUMNS = 5;
|
||||
private static final ResourceLocation PAGE_ATLAS = texture("gui/notebook_pages");
|
||||
private static final ResourceLocation GENERIC_SYMBOL = texture("item/page_symbol");
|
||||
private static final ResourceLocation UNRESOLVED_SYMBOL = texture("item/page_unresolved");
|
||||
private static final ResourceLocation STANDARD_TERRAIN = texture("item/page_symbol_terrain_standard");
|
||||
private static final ResourceLocation NATIVE_BIOMES = texture("item/page_symbol_biome_native");
|
||||
private static final ResourceLocation NORMAL_LIGHTING = texture("item/page_symbol_lighting_normal");
|
||||
private static final ResourceLocation NORMAL_WEATHER = texture("item/page_symbol_weather_normal");
|
||||
private static final ResourceLocation NORMAL_TIME = texture("item/page_symbol_celestial_normal_time");
|
||||
private static final ResourceLocation CAVES = texture("item/page_symbol_feature_caves");
|
||||
private EditBox textInput;
|
||||
private boolean draggingScrollbar;
|
||||
private boolean surfaceClickPendingRelease;
|
||||
@@ -262,7 +255,7 @@ public final class NotebookScreen extends AbstractContainerScreen<NotebookMenu>
|
||||
case BLANK -> { }
|
||||
case LINK_PANEL -> graphics.fill(x + 5, y + 6, x + 25, y + 20, 0xFF000000);
|
||||
case SYMBOL, UNRESOLVED_SYMBOL -> graphics.blit(symbolTexture(page), x + 1, y + 6,
|
||||
28, 28, 16, 16, 96, 96, 128, 128);
|
||||
28, 28, 0, 0, 128, 128, 128, 128);
|
||||
}
|
||||
}
|
||||
if (dimmed) {
|
||||
@@ -274,23 +267,7 @@ public final class NotebookScreen extends AbstractContainerScreen<NotebookMenu>
|
||||
}
|
||||
|
||||
static ResourceLocation symbolTexture(ItemStack page) {
|
||||
var content = PageItem.content(page);
|
||||
if (content.kind() == net.dnorberg.rivencraft.writing.PageContent.Kind.UNRESOLVED_SYMBOL) {
|
||||
return UNRESOLVED_SYMBOL;
|
||||
}
|
||||
return symbolTexture(content.symbol().orElseThrow().id());
|
||||
}
|
||||
|
||||
static ResourceLocation symbolTexture(ResourceLocation id) {
|
||||
return switch (id.toString()) {
|
||||
case "rivencraft:terrain/standard" -> STANDARD_TERRAIN;
|
||||
case "rivencraft:biome/native" -> NATIVE_BIOMES;
|
||||
case "rivencraft:lighting/normal" -> NORMAL_LIGHTING;
|
||||
case "rivencraft:weather/normal" -> NORMAL_WEATHER;
|
||||
case "rivencraft:celestial/normal_time" -> NORMAL_TIME;
|
||||
case "rivencraft:feature/caves" -> CAVES;
|
||||
default -> GENERIC_SYMBOL;
|
||||
};
|
||||
return SymbolPageTextures.forPage(page);
|
||||
}
|
||||
|
||||
private static ResourceLocation texture(String path) {
|
||||
|
||||
@@ -5,6 +5,7 @@ import java.util.Locale;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import net.dnorberg.rivencraft.Rivencraft;
|
||||
import net.dnorberg.rivencraft.client.SymbolPageTextures;
|
||||
import net.dnorberg.rivencraft.item.PageItem;
|
||||
import net.dnorberg.rivencraft.menu.WritingDeskMenu;
|
||||
import net.dnorberg.rivencraft.block.entity.WritingDeskBlockEntity;
|
||||
@@ -148,7 +149,8 @@ public final class WritingDeskScreen extends AbstractContainerScreen<WritingDesk
|
||||
if (tab == menu.activeCollection() && !allMode) graphics.setColor(0.5F, 0.5F, 1F, 1F);
|
||||
graphics.blit(LEGACY_DESK, x, tabY, 0, 166, 58, 37);
|
||||
graphics.setColor(1F, 1F, 1F, 1F);
|
||||
graphics.drawString(font, Integer.toString(tab + 1), x + 8, tabY + 8, 0xFF181818, false);
|
||||
// Encode the visible one-based tab number with Legacy's numeric word glyph.
|
||||
SymbolPageTextures.drawNumeral(graphics, tab + 1, x + 8, tabY + 3, 19);
|
||||
ItemStack collection = menu.getSlot(tab).getItem();
|
||||
if (!collection.isEmpty()) {
|
||||
String name = collection.getHoverName().getString();
|
||||
@@ -220,6 +222,11 @@ public final class WritingDeskScreen extends AbstractContainerScreen<WritingDesk
|
||||
graphics.renderTooltip(font, Component.translatable("screen.rivencraft.writing_desk.previous_tabs"), mouseX, mouseY);
|
||||
else if (localY >= 177 && localY < 186)
|
||||
graphics.renderTooltip(font, Component.translatable("screen.rivencraft.writing_desk.next_tabs"), mouseX, mouseY);
|
||||
else if (mouseX < leftPos + 34 && localY >= 29 && localY < 177) {
|
||||
int tab = menu.firstVisibleTab() + (localY - 29) / 37;
|
||||
graphics.renderTooltip(font, Component.translatable("screen.rivencraft.writing_desk.tab_symbol",
|
||||
tab + 1), mouseX, mouseY);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -270,7 +277,7 @@ public final class WritingDeskScreen extends AbstractContainerScreen<WritingDesk
|
||||
case BLANK -> { }
|
||||
case LINK_PANEL -> graphics.fill(x + 5, y + 6, x + 25, y + 20, 0xFF000000);
|
||||
case SYMBOL, UNRESOLVED_SYMBOL -> graphics.blit(NotebookScreen.symbolTexture(page), x + 1, y + 6,
|
||||
28, 28, 16, 16, 96, 96, 128, 128);
|
||||
28, 28, 0, 0, 128, 128, 128, 128);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ package net.dnorberg.rivencraft.gametest;
|
||||
import com.google.gson.JsonParser;
|
||||
import com.mojang.serialization.JsonOps;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import net.dnorberg.rivencraft.book.AgeReference;
|
||||
@@ -11,6 +12,8 @@ import net.dnorberg.rivencraft.writing.PageContent;
|
||||
import net.dnorberg.rivencraft.writing.SymbolCatalog;
|
||||
import net.dnorberg.rivencraft.writing.SymbolDefinition;
|
||||
import net.dnorberg.rivencraft.writing.SymbolSemantics;
|
||||
import net.dnorberg.rivencraft.writing.SymbolPoems;
|
||||
import net.dnorberg.rivencraft.writing.SymbolNumerals;
|
||||
import net.dnorberg.rivencraft.writing.SymbolReference;
|
||||
import net.dnorberg.rivencraft.writing.WritingEntry;
|
||||
import net.dnorberg.rivencraft.writing.NotebookContents;
|
||||
@@ -109,6 +112,20 @@ public final class WritingDataGameTests {
|
||||
helper.assertValueEqual(builtIn.schemaVersion(), 2, "Built-in symbol must use semantic schema v2");
|
||||
helper.assertValueEqual(builtIn.semantics().orElseThrow().role(), SymbolSemantics.Role.TERRAIN,
|
||||
"Built-in terrain symbol must declare its grammar role");
|
||||
helper.assertValueEqual(builtIn.poem(), "Terrain Form Tradition Flow",
|
||||
"Built-in page artwork must carry its four-word Legacy poem");
|
||||
helper.assertValueEqual(SymbolPoems.builtIn(reference("rivencraft:terrain/standard").id()), builtIn.poem(),
|
||||
"Pre-artwork saved starter pages must retain their original poem");
|
||||
helper.assertTrue(Arrays.equals(SymbolNumerals.componentsForDigit(0), new int[] {1}),
|
||||
"Zero must use Legacy's dedicated number mark");
|
||||
helper.assertTrue(Arrays.equals(SymbolNumerals.componentsForDigit(1), new int[] {56}),
|
||||
"One must use Legacy's first bottom-row mark");
|
||||
helper.assertTrue(Arrays.equals(SymbolNumerals.componentsForDigit(24), new int[] {63, 59}),
|
||||
"Twenty-four must combine Legacy's five and unit marks");
|
||||
helper.assertTrue(Arrays.equals(SymbolNumerals.digitsFor(25), new int[] {1, 0}),
|
||||
"Twenty-five must start a second base-25 place");
|
||||
helper.assertTrue(Arrays.equals(SymbolNumerals.digitsFor(88), new int[] {3, 13}),
|
||||
"Last writing-desk tab must retain its numeric value");
|
||||
|
||||
String display = "\"name\":\"symbol.example.test\","
|
||||
+ "\"description\":\"symbol.example.test.description\","
|
||||
@@ -146,6 +163,10 @@ public final class WritingDataGameTests {
|
||||
.encodeStart(JsonOps.INSTANCE, clientJson).result().orElseThrow();
|
||||
helper.assertFalse(encoded.getAsJsonObject().has("semantics"),
|
||||
"Client display payload must never contain generation semantics");
|
||||
helper.assertValueEqual(encoded.getAsJsonObject().get("poem").getAsString(), builtIn.poem(),
|
||||
"Client display payload must carry page artwork metadata");
|
||||
rejectDefinition(helper, "{\"schema_version\":2," + display
|
||||
+ ",\"semantics\":{\"role\":\"weather\",\"value\":\"normal\"},\"poem\":\"only two\"}", "poem");
|
||||
helper.succeed();
|
||||
}
|
||||
|
||||
|
||||
@@ -8,10 +8,10 @@ import net.dnorberg.rivencraft.writing.PageContent;
|
||||
import net.dnorberg.rivencraft.writing.SymbolCatalog;
|
||||
import net.dnorberg.rivencraft.writing.SymbolDisplayMetadata;
|
||||
import net.dnorberg.rivencraft.writing.SymbolReference;
|
||||
import net.dnorberg.rivencraft.writing.SymbolPoems;
|
||||
import net.minecraft.ChatFormatting;
|
||||
import net.minecraft.core.component.DataComponents;
|
||||
import net.minecraft.network.chat.Component;
|
||||
import net.minecraft.world.item.component.CustomModelData;
|
||||
import net.minecraft.world.item.Item;
|
||||
import net.minecraft.world.item.ItemStack;
|
||||
import net.minecraft.world.item.TooltipFlag;
|
||||
@@ -42,7 +42,7 @@ public final class PageItem extends Item {
|
||||
page.set(ModDataComponents.PAGE_SYMBOL_DISPLAY.get(), new SymbolDisplayMetadata(
|
||||
reference.id(), key, key + ".description",
|
||||
net.minecraft.resources.ResourceLocation.fromNamespaceAndPath(Rivencraft.MOD_ID, path.split("/")[0]),
|
||||
true));
|
||||
true, SymbolPoems.builtIn(reference.id())));
|
||||
return page;
|
||||
}
|
||||
|
||||
@@ -118,7 +118,6 @@ public final class PageItem extends Item {
|
||||
private static ItemStack create(PageContent content) {
|
||||
ItemStack stack = new ItemStack(ModItems.PAGE.get());
|
||||
stack.set(ModDataComponents.PAGE_CONTENT.get(), content);
|
||||
stack.set(DataComponents.CUSTOM_MODEL_DATA, new CustomModelData(modelIndex(content)));
|
||||
if (content.kind() == PageContent.Kind.SYMBOL) {
|
||||
stack.set(ModDataComponents.PAGE_SYMBOL_DISPLAY.get(),
|
||||
SymbolCatalog.display(content.symbol().orElseThrow()));
|
||||
@@ -134,20 +133,4 @@ public final class PageItem extends Item {
|
||||
: Component.translatable("item.rivencraft.page.unresolved");
|
||||
}
|
||||
|
||||
private static int modelIndex(PageContent content) {
|
||||
return switch (content.kind()) {
|
||||
case BLANK -> 0;
|
||||
case LINK_PANEL -> 1;
|
||||
case UNRESOLVED_SYMBOL -> 2;
|
||||
case SYMBOL -> switch (content.symbol().orElseThrow().id().toString()) {
|
||||
case "rivencraft:terrain/standard" -> 100;
|
||||
case "rivencraft:biome/native" -> 101;
|
||||
case "rivencraft:lighting/normal" -> 102;
|
||||
case "rivencraft:weather/normal" -> 103;
|
||||
case "rivencraft:celestial/normal_time" -> 104;
|
||||
case "rivencraft:feature/caves" -> 105;
|
||||
default -> 3;
|
||||
};
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -15,18 +15,20 @@ public record SymbolDefinition(
|
||||
String nameTranslationKey,
|
||||
String descriptionTranslationKey,
|
||||
ResourceLocation category,
|
||||
Optional<SymbolSemantics> semantics
|
||||
Optional<SymbolSemantics> semantics,
|
||||
String poem
|
||||
) {
|
||||
public static final int CURRENT_SCHEMA_VERSION = 2;
|
||||
private static final Pattern TRANSLATION_KEY = Pattern.compile("[a-z0-9_.-]+");
|
||||
private static final Set<String> V2_FIELDS = Set.of(
|
||||
"schema_version", "name", "description", "category", "semantics");
|
||||
"schema_version", "name", "description", "category", "semantics", "poem");
|
||||
|
||||
public SymbolDefinition {
|
||||
Objects.requireNonNull(nameTranslationKey, "nameTranslationKey");
|
||||
Objects.requireNonNull(descriptionTranslationKey, "descriptionTranslationKey");
|
||||
Objects.requireNonNull(category, "category");
|
||||
semantics = Objects.requireNonNull(semantics, "semantics");
|
||||
poem = Objects.requireNonNull(poem, "poem");
|
||||
if (schemaVersion != 1 && schemaVersion != CURRENT_SCHEMA_VERSION) {
|
||||
throw new IllegalArgumentException("Unsupported schema_version " + schemaVersion);
|
||||
}
|
||||
@@ -38,6 +40,10 @@ public record SymbolDefinition(
|
||||
}
|
||||
validateTranslationKey(nameTranslationKey, "name");
|
||||
validateTranslationKey(descriptionTranslationKey, "description");
|
||||
if (!poem.isEmpty() && (poem.length() > 96
|
||||
|| !poem.matches("[A-Za-z]+(?: [A-Za-z]+){3}"))) {
|
||||
throw new IllegalArgumentException("'poem' must contain exactly four alphabetic words");
|
||||
}
|
||||
}
|
||||
|
||||
/** v1 remains display-only; v2 has an exact field set and typed semantics. */
|
||||
@@ -66,11 +72,12 @@ public record SymbolDefinition(
|
||||
Optional<SymbolSemantics> semantics = version == CURRENT_SCHEMA_VERSION
|
||||
? Optional.of(SymbolSemantics.parse(object.get("semantics")))
|
||||
: Optional.empty();
|
||||
return new SymbolDefinition(version, name, description, category, semantics);
|
||||
String poem = object.has("poem") ? requiredString(object, "poem") : "";
|
||||
return new SymbolDefinition(version, name, description, category, semantics, poem);
|
||||
}
|
||||
|
||||
public SymbolDisplayMetadata display(ResourceLocation id) {
|
||||
return new SymbolDisplayMetadata(id, nameTranslationKey, descriptionTranslationKey, category, true);
|
||||
return new SymbolDisplayMetadata(id, nameTranslationKey, descriptionTranslationKey, category, true, poem);
|
||||
}
|
||||
|
||||
static String requiredString(JsonObject object, String field) {
|
||||
|
||||
@@ -14,14 +14,16 @@ public record SymbolDisplayMetadata(
|
||||
String nameTranslationKey,
|
||||
String descriptionTranslationKey,
|
||||
ResourceLocation category,
|
||||
boolean resolved
|
||||
boolean resolved,
|
||||
String poem
|
||||
) {
|
||||
public static final Codec<SymbolDisplayMetadata> CODEC = RecordCodecBuilder.create(instance -> instance.group(
|
||||
ResourceLocation.CODEC.fieldOf("id").forGetter(SymbolDisplayMetadata::id),
|
||||
Codec.STRING.fieldOf("name").forGetter(SymbolDisplayMetadata::nameTranslationKey),
|
||||
Codec.STRING.fieldOf("description").forGetter(SymbolDisplayMetadata::descriptionTranslationKey),
|
||||
ResourceLocation.CODEC.fieldOf("category").forGetter(SymbolDisplayMetadata::category),
|
||||
Codec.BOOL.fieldOf("resolved").forGetter(SymbolDisplayMetadata::resolved)
|
||||
Codec.BOOL.fieldOf("resolved").forGetter(SymbolDisplayMetadata::resolved),
|
||||
Codec.STRING.optionalFieldOf("poem", "").forGetter(SymbolDisplayMetadata::poem)
|
||||
).apply(instance, SymbolDisplayMetadata::new));
|
||||
public static final StreamCodec<ByteBuf, SymbolDisplayMetadata> STREAM_CODEC = StreamCodec.composite(
|
||||
ResourceLocation.STREAM_CODEC, SymbolDisplayMetadata::id,
|
||||
@@ -29,6 +31,7 @@ public record SymbolDisplayMetadata(
|
||||
ByteBufCodecs.STRING_UTF8, SymbolDisplayMetadata::descriptionTranslationKey,
|
||||
ResourceLocation.STREAM_CODEC, SymbolDisplayMetadata::category,
|
||||
ByteBufCodecs.BOOL, SymbolDisplayMetadata::resolved,
|
||||
ByteBufCodecs.STRING_UTF8, SymbolDisplayMetadata::poem,
|
||||
SymbolDisplayMetadata::new
|
||||
);
|
||||
|
||||
@@ -38,7 +41,8 @@ public record SymbolDisplayMetadata(
|
||||
"symbol.rivencraft.unresolved",
|
||||
"symbol.rivencraft.unresolved.description",
|
||||
ResourceLocation.fromNamespaceAndPath(Rivencraft.MOD_ID, "unresolved"),
|
||||
false
|
||||
false,
|
||||
""
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package net.dnorberg.rivencraft.writing;
|
||||
|
||||
/**
|
||||
* Number components from Mystcraft Legacy's ModWords#constructNumber.
|
||||
* Legacy registers 0–25; larger Rivencraft tab numbers use positional base 25.
|
||||
*/
|
||||
public final class SymbolNumerals {
|
||||
private SymbolNumerals() {}
|
||||
|
||||
public static int[] digitsFor(int number) {
|
||||
if (number < 0) throw new IllegalArgumentException("Numeral must be non-negative");
|
||||
if (number < 25) return new int[] {number};
|
||||
int count = 0;
|
||||
for (int remaining = number; remaining > 0; remaining /= 25) count++;
|
||||
int[] digits = new int[count];
|
||||
for (int remaining = number, index = count - 1; index >= 0; index--, remaining /= 25)
|
||||
digits[index] = remaining % 25;
|
||||
return digits;
|
||||
}
|
||||
|
||||
public static int[] componentsForDigit(int digit) {
|
||||
if (digit < 0 || digit >= 25) throw new IllegalArgumentException("Numeral digit must be in [0, 24]");
|
||||
if (digit == 0) return new int[] {1};
|
||||
int fifths = digit / 5;
|
||||
int remainder = digit % 5;
|
||||
if (fifths == 0) return new int[] {55 + remainder};
|
||||
if (remainder == 0) return new int[] {59 + fifths};
|
||||
return new int[] {59 + fifths, 55 + remainder};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package net.dnorberg.rivencraft.writing;
|
||||
|
||||
import net.minecraft.resources.ResourceLocation;
|
||||
|
||||
/** Legacy poems for existing starter pages, including stacks saved before poem metadata existed. */
|
||||
public final class SymbolPoems {
|
||||
private SymbolPoems() {}
|
||||
|
||||
public static String builtIn(ResourceLocation id) {
|
||||
if (!id.getNamespace().equals("rivencraft")) return "";
|
||||
return switch (id.getPath()) {
|
||||
case "terrain/standard" -> "Terrain Form Tradition Flow";
|
||||
case "biome/native" -> "Constraint Nature Tradition Sustain";
|
||||
case "lighting/normal" -> "Ethereal Dynamic Cycle Balance";
|
||||
case "weather/normal" -> "Sustain Dynamic Tradition Balance";
|
||||
case "celestial/normal_time" -> "Transform Time System Balance";
|
||||
case "feature/caves" -> "Terrain Transform Void Flow";
|
||||
default -> "";
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -42,6 +42,7 @@
|
||||
"screen.rivencraft.writing_desk.all_categories": "All categories",
|
||||
"screen.rivencraft.writing_desk.previous_tabs": "Previous four tabs (Page Up)",
|
||||
"screen.rivencraft.writing_desk.next_tabs": "Next four tabs (Page Down)",
|
||||
"screen.rivencraft.writing_desk.tab_symbol": "Tab %s",
|
||||
"screen.rivencraft.writing_desk.no_symbols": "No available symbols",
|
||||
"item.rivencraft.ink_vial.remaining": "Ink: %s/%s",
|
||||
"item.rivencraft.ink_vial.usage": "Use with paper in the other hand to prepare a Link Panel.",
|
||||
|
||||
@@ -1,17 +1,13 @@
|
||||
{
|
||||
"parent": "minecraft:item/generated",
|
||||
"textures": {
|
||||
"layer0": "rivencraft:item/page"
|
||||
},
|
||||
"overrides": [
|
||||
{ "predicate": { "custom_model_data": 1 }, "model": "rivencraft:item/page_link_panel" },
|
||||
{ "predicate": { "custom_model_data": 2 }, "model": "rivencraft:item/page_unresolved" },
|
||||
{ "predicate": { "custom_model_data": 3 }, "model": "rivencraft:item/page_symbol" },
|
||||
{ "predicate": { "custom_model_data": 100 }, "model": "rivencraft:item/page_symbol_terrain_standard" },
|
||||
{ "predicate": { "custom_model_data": 101 }, "model": "rivencraft:item/page_symbol_biome_native" },
|
||||
{ "predicate": { "custom_model_data": 102 }, "model": "rivencraft:item/page_symbol_lighting_normal" },
|
||||
{ "predicate": { "custom_model_data": 103 }, "model": "rivencraft:item/page_symbol_weather_normal" },
|
||||
{ "predicate": { "custom_model_data": 104 }, "model": "rivencraft:item/page_symbol_celestial_normal_time" },
|
||||
{ "predicate": { "custom_model_data": 105 }, "model": "rivencraft:item/page_symbol_feature_caves" }
|
||||
]
|
||||
"parent": "builtin/entity",
|
||||
"gui_light": "front",
|
||||
"display": {
|
||||
"ground": { "translation": [0, 2, 0], "scale": [0.5, 0.5, 0.5] },
|
||||
"head": { "rotation": [0, 180, 0], "translation": [0, 13, 7] },
|
||||
"thirdperson_righthand": { "translation": [0, 3, 1], "scale": [0.55, 0.55, 0.55] },
|
||||
"thirdperson_lefthand": { "translation": [0, 3, 1], "scale": [0.55, 0.55, 0.55] },
|
||||
"firstperson_righthand": { "rotation": [0, -90, 25], "translation": [1.13, 3.2, 1.13], "scale": [0.68, 0.68, 0.68] },
|
||||
"firstperson_lefthand": { "rotation": [0, 90, -25], "translation": [1.13, 3.2, 1.13], "scale": [0.68, 0.68, 0.68] },
|
||||
"fixed": { "rotation": [0, 180, 0] }
|
||||
}
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"parent": "minecraft:item/generated",
|
||||
"textures": { "layer0": "rivencraft:item/page_symbol_biome_native" }
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"parent": "minecraft:item/generated",
|
||||
"textures": { "layer0": "rivencraft:item/page_symbol_celestial_normal_time" }
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"parent": "minecraft:item/generated",
|
||||
"textures": { "layer0": "rivencraft:item/page_symbol_feature_caves" }
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"parent": "minecraft:item/generated",
|
||||
"textures": { "layer0": "rivencraft:item/page_symbol_lighting_normal" }
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"parent": "minecraft:item/generated",
|
||||
"textures": { "layer0": "rivencraft:item/page_symbol_terrain_standard" }
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
{
|
||||
"parent": "minecraft:item/generated",
|
||||
"textures": { "layer0": "rivencraft:item/page_symbol_weather_normal" }
|
||||
}
|
||||
|
After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 12 KiB |
@@ -3,5 +3,6 @@
|
||||
"name": "symbol.rivencraft.biome.native",
|
||||
"description": "symbol.rivencraft.biome.native.description",
|
||||
"category": "rivencraft:biome",
|
||||
"semantics": { "role": "biome_controller", "value": "native" }
|
||||
"semantics": { "role": "biome_controller", "value": "native" },
|
||||
"poem": "Constraint Nature Tradition Sustain"
|
||||
}
|
||||
@@ -3,5 +3,6 @@
|
||||
"name": "symbol.rivencraft.celestial.normal_time",
|
||||
"description": "symbol.rivencraft.celestial.normal_time.description",
|
||||
"category": "rivencraft:celestial",
|
||||
"semantics": { "role": "celestial", "value": "normal_time" }
|
||||
"semantics": { "role": "celestial", "value": "normal_time" },
|
||||
"poem": "Transform Time System Balance"
|
||||
}
|
||||
@@ -3,5 +3,6 @@
|
||||
"name": "symbol.rivencraft.feature.caves",
|
||||
"description": "symbol.rivencraft.feature.caves.description",
|
||||
"category": "rivencraft:feature",
|
||||
"semantics": { "role": "feature", "value": "rivencraft:caves" }
|
||||
"semantics": { "role": "feature", "value": "rivencraft:caves" },
|
||||
"poem": "Terrain Transform Void Flow"
|
||||
}
|
||||
@@ -3,5 +3,6 @@
|
||||
"name": "symbol.rivencraft.lighting.normal",
|
||||
"description": "symbol.rivencraft.lighting.normal.description",
|
||||
"category": "rivencraft:lighting",
|
||||
"semantics": { "role": "lighting", "value": "normal" }
|
||||
"semantics": { "role": "lighting", "value": "normal" },
|
||||
"poem": "Ethereal Dynamic Cycle Balance"
|
||||
}
|
||||
@@ -3,5 +3,6 @@
|
||||
"name": "symbol.rivencraft.terrain.standard",
|
||||
"description": "symbol.rivencraft.terrain.standard.description",
|
||||
"category": "rivencraft:terrain",
|
||||
"semantics": { "role": "terrain", "value": "rivencraft:standard" }
|
||||
"semantics": { "role": "terrain", "value": "rivencraft:standard" },
|
||||
"poem": "Terrain Form Tradition Flow"
|
||||
}
|
||||
@@ -3,5 +3,6 @@
|
||||
"name": "symbol.rivencraft.weather.normal",
|
||||
"description": "symbol.rivencraft.weather.normal.description",
|
||||
"category": "rivencraft:weather",
|
||||
"semantics": { "role": "weather", "value": "normal" }
|
||||
"semantics": { "role": "weather", "value": "normal" },
|
||||
"poem": "Sustain Dynamic Tradition Balance"
|
||||
}
|
||||