Add tabs with chapters and pages to the spellbook, add a profile page with character levels and mana, and contents for the introduction chapter

This commit is contained in:
Sollace 2022-08-31 23:58:49 +02:00
parent a73a7af43c
commit 978d336b96
31 changed files with 1048 additions and 145 deletions

View file

@ -11,19 +11,20 @@ import com.minelittlepony.unicopia.Unicopia;
import com.minelittlepony.unicopia.client.gui.LanSettingsScreen;
import com.minelittlepony.unicopia.client.gui.UHud;
import com.minelittlepony.unicopia.client.minelittlepony.MineLPConnector;
import com.minelittlepony.unicopia.container.SpellbookScreen;
import com.minelittlepony.unicopia.container.UScreenHandlers;
import com.minelittlepony.unicopia.container.*;
import com.minelittlepony.unicopia.entity.player.PlayerCamera;
import com.minelittlepony.unicopia.entity.player.Pony;
import net.fabricmc.api.ClientModInitializer;
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
import net.fabricmc.fabric.api.client.item.v1.ItemTooltipCallback;
import net.fabricmc.fabric.api.resource.ResourceManagerHelper;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.gui.screen.OpenToLanScreen;
import net.minecraft.client.gui.screen.Screen;
import net.minecraft.client.gui.screen.ingame.HandledScreens;
import net.minecraft.client.gui.screen.world.CreateWorldScreen;
import net.minecraft.entity.player.PlayerEntity;
import net.minecraft.resource.ResourceType;
import net.minecraft.text.Text;
public class UnicopiaClient implements ClientModInitializer {
@ -63,6 +64,7 @@ public class UnicopiaClient implements ClientModInitializer {
URenderers.bootstrap();
HandledScreens.register(UScreenHandlers.SPELL_BOOK, SpellbookScreen::new);
ResourceManagerHelper.get(ResourceType.CLIENT_RESOURCES).registerReloadListener(SpellbookChapterLoader.INSTANCE);
ClientTickEvents.END_CLIENT_TICK.register(this::onTick);
ScreenInitCallback.EVENT.register(this::onScreenInit);

View file

@ -14,14 +14,13 @@ import net.minecraft.item.ItemStack;
import net.minecraft.util.math.MathHelper;
import net.minecraft.util.math.Matrix4f;
public class DrawableUtil {
public static final double PI = Math.PI;
public static final double TAU = Math.PI * 2;
private static final double NUM_RINGS = 300;
private static final double INCREMENT = TAU / NUM_RINGS;
public interface DrawableUtil {
double PI = Math.PI;
double TAU = Math.PI * 2;
double NUM_RINGS = 300;
double INCREMENT = TAU / NUM_RINGS;
public static void renderItemIcon(ItemStack stack, double x, double y, float scale) {
static void renderItemIcon(ItemStack stack, double x, double y, float scale) {
MatrixStack modelStack = RenderSystem.getModelViewStack();
modelStack.push();
modelStack.translate(x, y, 0);
@ -36,7 +35,7 @@ public class DrawableUtil {
RenderSystem.applyModelViewMatrix();
}
public static void drawLine(MatrixStack matrices, int x1, int y1, int x2, int y2, int color) {
static void drawLine(MatrixStack matrices, int x1, int y1, int x2, int y2, int color) {
RenderSystem.enableBlend();
RenderSystem.disableTexture();
RenderSystem.defaultBlendFunc();
@ -66,7 +65,7 @@ public class DrawableUtil {
*
* @param mirrorHorizontally Whether or not the arc must be mirrored across the horizontal plane. Will produce a bar that grows from the middle filling both sides.
*/
public static void drawArc(MatrixStack matrices, double innerRadius, double outerRadius, double startAngle, double arcAngle, int color, boolean mirrorHorizontally) {
static void drawArc(MatrixStack matrices, double innerRadius, double outerRadius, double startAngle, double arcAngle, int color, boolean mirrorHorizontally) {
float r = (color >> 24 & 255) / 255F;
float g = (color >> 16 & 255) / 255F;
float b = (color >> 8 & 255) / 255F;
@ -113,7 +112,7 @@ public class DrawableUtil {
*
* @param mirrorHorizontally Whether or not the arc must be mirrored across the horizontal plane. Will produce a bar that grows from the middle filling both sides.
*/
public static void drawArc(MatrixStack matrices, double radius, double startAngle, double arcAngle, int color, boolean mirrorHorizontally) {
static void drawArc(MatrixStack matrices, double radius, double startAngle, double arcAngle, int color, boolean mirrorHorizontally) {
drawCircle(matrices, radius, startAngle, arcAngle, color, mirrorHorizontally, VertexFormat.DrawMode.DEBUG_LINES);
}
@ -122,7 +121,7 @@ public class DrawableUtil {
*
* @param mirrorHorizontally Whether or not the arc must be mirrored across the horizontal plane. Will produce a bar that grows from the middle filling both sides.
*/
public static void drawCircle(MatrixStack matrices, double radius, double startAngle, double arcAngle, int color, boolean mirrorHorizontally) {
static void drawCircle(MatrixStack matrices, double radius, double startAngle, double arcAngle, int color, boolean mirrorHorizontally) {
drawCircle(matrices, radius, startAngle, arcAngle, color, mirrorHorizontally, VertexFormat.DrawMode.QUADS);
}

View file

@ -0,0 +1,183 @@
package com.minelittlepony.unicopia.container;
import java.util.*;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;
import com.minelittlepony.common.client.gui.dimension.Bounds;
import com.minelittlepony.unicopia.container.SpellbookChapterList.Content;
import com.minelittlepony.unicopia.container.SpellbookChapterList.Draw;
import com.minelittlepony.unicopia.entity.player.Pony;
import com.mojang.blaze3d.systems.RenderSystem;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.font.TextRenderer;
import net.minecraft.client.gui.DrawableHelper;
import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.text.Style;
import net.minecraft.text.Text;
import net.minecraft.util.*;
import net.minecraft.util.math.MathHelper;
public class DynamicContent implements Content {
private int offset = 0;
private final List<Page> pages = new ArrayList<>();
Bounds bounds = Bounds.empty();
public DynamicContent(JsonArray pages) {
pages.forEach(page -> this.pages.add(new Page(page.getAsJsonObject())));
}
@Override
public void draw(MatrixStack matrices, int mouseX, int mouseY) {
int pageIndex = offset * 2;
getPage(pageIndex).ifPresent(page -> page.draw(matrices, mouseX, mouseY));
matrices.push();
matrices.translate(bounds.width / 2 + 20, 0, 0);
getPage(pageIndex + 1).ifPresent(page -> page.draw(matrices, mouseX, mouseY));
matrices.pop();
}
@Override
public void copyStateFrom(Content old) {
if (old instanceof DynamicContent o) {
offset = o.offset;
bounds = o.bounds;
}
}
private Optional<Page> getPage(int index) {
if (index < 0 || index >= pages.size()) {
return Optional.empty();
}
return Optional.of(pages.get(index));
}
@Override
public void init(SpellbookScreen screen) {
bounds = screen.getFrameBounds();
pages.forEach(Page::reset);
screen.addPageButtons(187, 30, 350, incr -> {
offset = MathHelper.clamp(offset + incr, 0, (int)Math.ceil(pages.size() / 2F) - 1);
});
}
class Page implements Draw {
private final List<Page.Paragraph> paragraphs = new ArrayList<>();
private final List<Page.Image> images = new ArrayList<>();
private boolean compiled = false;
private final List<Text> wrappedText = new ArrayList<>();
private final Text title;
private final int level;
public Page(JsonObject json) {
title = Text.Serializer.fromJson(json.get("title"));
level = JsonHelper.getInt(json, "level", 0);
int[] lineNumber = new int[1];
JsonHelper.getArray(json, "elements", new JsonArray()).forEach(element -> {
if (element.isJsonPrimitive()) {
paragraphs.add(new Paragraph(lineNumber[0], Text.Serializer.fromJson(element)));
} else {
JsonObject image = JsonHelper.asObject(element, "element");
if (image.has("texture")) {
images.add(new Image(
new Identifier(JsonHelper.getString(image, "texture")),
new Bounds(
JsonHelper.getInt(image, "y", 0),
JsonHelper.getInt(image, "x", 0),
JsonHelper.getInt(image, "width", 0),
JsonHelper.getInt(image, "height", 0)
)
));
} else {
paragraphs.add(new Paragraph(lineNumber[0], Text.Serializer.fromJson(element)));
}
}
lineNumber[0]++;
});
}
public void compile() {
if (!compiled) {
compiled = true;
wrappedText.clear();
ParagraphWrappingVisitor visitor = new ParagraphWrappingVisitor(this, yPosition -> {
return (bounds.width / 2 - 10) - images.stream()
.map(Image::bounds)
.filter(b -> b.contains(b.left + b.width / 2, yPosition))
.mapToInt(b -> b.width)
.max()
.orElse(0);
}, wrappedText::add);
paragraphs.forEach(paragraph -> {
paragraph.text().visit(visitor, Style.EMPTY);
visitor.forceAdvance();
});
}
}
public void reset() {
compiled = false;
}
@Override
public void draw(MatrixStack matrices, int mouseX, int mouseY) {
compile();
TextRenderer font = MinecraftClient.getInstance().textRenderer;
boolean needsMoreXp = Pony.of(MinecraftClient.getInstance().player).getLevel().get() < level;
matrices.push();
matrices.translate(bounds.left, bounds.top - 10, mouseY);
matrices.scale(1.3F, 1.3F, 1);
font.draw(matrices, needsMoreXp ? Text.of("???") : title, 0, 0, mouseY);
matrices.pop();
matrices.push();
matrices.translate(bounds.left, bounds.top - 10, mouseY);
matrices.translate(0, 12, 0);
matrices.scale(0.8F, 0.8F, 1);
font.draw(matrices, Text.literal("Level: " + (level + 1)).formatted(Formatting.DARK_GREEN), 0, 0, mouseY);
matrices.pop();
matrices.push();
matrices.translate(0, 16, 0);
for (int y = 0; y < wrappedText.size(); y++) {
Text line = wrappedText.get(y);
if (needsMoreXp) {
line = line.copy().formatted(Formatting.OBFUSCATED);
}
font.draw(matrices, line,
bounds.left,
bounds.top + (y * font.fontHeight),
0
);
}
matrices.translate(bounds.left, bounds.top, 0);
images.forEach(image -> image.draw(matrices, mouseX, mouseY));
matrices.pop();
}
public record Paragraph(int y, Text text) {}
public record Image(
Identifier texture,
Bounds bounds) implements Draw {
@Override
public void draw(MatrixStack matrices, int mouseX, int mouseY) {
RenderSystem.setShaderTexture(0, texture);
DrawableHelper.drawTexture(matrices, bounds().left, bounds().top, 0, 0, 0, bounds().width, bounds().height, bounds().width, bounds().height);
RenderSystem.setShaderTexture(0, SpellbookScreen.TEXTURE);
}
}
}
}

View file

@ -0,0 +1,86 @@
package com.minelittlepony.unicopia.container;
import java.util.*;
import java.util.function.Consumer;
import it.unimi.dsi.fastutil.ints.Int2IntFunction;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.font.TextHandler;
import net.minecraft.client.font.TextRenderer;
import net.minecraft.text.*;
import net.minecraft.text.StringVisitable.StyledVisitor;
class ParagraphWrappingVisitor implements StyledVisitor<Object> {
private int line = 0;
private int pageWidth;
private final TextRenderer font = MinecraftClient.getInstance().textRenderer;
private final TextHandler handler = font.getTextHandler();
private float currentLineCollectedLength = 0;
private MutableText currentLine = Text.empty();
private boolean progressedNonEmpty;
private final Int2IntFunction widthSupplier;
private final Consumer<Text> lineConsumer;
ParagraphWrappingVisitor(DynamicContent.Page page, Int2IntFunction widthSupplier, Consumer<Text> lineConsumer) {
this.widthSupplier = widthSupplier;
this.lineConsumer = lineConsumer;
pageWidth = widthSupplier.applyAsInt((line) * font.fontHeight);
}
@Override
public Optional<Object> accept(Style style, String s) {
while (!s.isEmpty()) {
int trimmedLength = handler.getTrimmedLength(s, pageWidth, style);
if (trimmedLength == 0) {
trimmedLength = s.length();
}
String wrappedFragment = s.substring(0, trimmedLength);
int lastSpace = wrappedFragment.lastIndexOf(' ');
trimmedLength = lastSpace > 0 ? Math.min(lastSpace, trimmedLength) : trimmedLength;
Text fragment = Text.literal(s.substring(0, trimmedLength).trim()).setStyle(style);
float grabbedWidth = handler.getWidth(fragment);
if (currentLineCollectedLength + grabbedWidth > pageWidth) {
advance();
}
if (currentLineCollectedLength > 0) {
currentLine.append(" ");
}
currentLine.append(fragment);
currentLineCollectedLength += grabbedWidth;
if (trimmedLength <= s.length()) {
s = s.substring(trimmedLength, s.length());
}
}
return Optional.empty();
}
public void forceAdvance() {
if (currentLineCollectedLength > 0) {
advance();
}
advance();
}
public void advance() {
if (progressedNonEmpty || currentLineCollectedLength > 0) {
progressedNonEmpty = true;
lineConsumer.accept(currentLine);
}
pageWidth = widthSupplier.applyAsInt((++line) * font.fontHeight);
currentLine = Text.empty();
currentLineCollectedLength = 0;
}
record StyledString (String string, Style style) {}
}

View file

@ -0,0 +1,91 @@
package com.minelittlepony.unicopia.container;
import java.util.*;
import java.util.function.Consumer;
import java.util.stream.Stream;
import com.minelittlepony.unicopia.Unicopia;
import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.util.Identifier;
public class SpellbookChapterList {
public static final Identifier CRAFTING_ID = Unicopia.id("crafting");
public static final Identifier PROFILE_ID = Unicopia.id("profile");
private final Chapter craftingChapter;
private Optional<Identifier> currentChapter = Optional.empty();
private final Map<Identifier, Chapter> chapters = new HashMap<>();
public SpellbookChapterList(Chapter craftingChapter, Chapter profileChapter) {
this.craftingChapter = craftingChapter;
SpellbookChapterLoader.INSTANCE.getChapters().forEach(chapter -> {
chapters.put(chapter.id(), chapter);
});
chapters.put(craftingChapter.id(), craftingChapter);
chapters.put(profileChapter.id(), profileChapter);
}
public Stream<Chapter> getTabs(TabSide side) {
return chapters.values().stream().filter(chapter -> chapter.side() == side);
}
public Chapter getCurrentChapter() {
if (SpellbookChapterLoader.DEBUG) {
SpellbookChapterLoader.INSTANCE.getChapters().forEach(chapter -> {
Optional.ofNullable(chapters.get(chapter.id())).flatMap(Chapter::content).ifPresent(old -> {
chapter.content().ifPresent(neu -> neu.copyStateFrom(old));
});
chapters.put(chapter.id(), chapter);
});
}
return currentChapter.map(chapters::get).orElse(craftingChapter);
}
public void setCurrentChapter(Chapter chapter) {
currentChapter = Optional.of(chapter.id());
}
public record Chapter (
Identifier id,
TabSide side,
int tabY,
int color,
Optional<Content> content) {
public Identifier icon() {
return new Identifier(id().getNamespace(), "textures/gui/container/pages/" + id().getPath() + ".png");
}
}
public enum TabSide {
LEFT,
RIGHT
}
public interface Content extends Draw {
void init(SpellbookScreen screen);
default void copyStateFrom(Content old) {}
static Optional<Content> of(Consumer<SpellbookScreen> init, Draw draw) {
return Optional.of(new Content() {
@Override
public void init(SpellbookScreen screen) {
init.accept(screen);
}
@Override
public void draw(MatrixStack matrices, int mouseX, int mouseY) {
draw.draw(matrices, mouseX, mouseY);
}
});
}
}
public interface Draw {
void draw(MatrixStack matrices, int mouseX, int mouseY);
}
}

View file

@ -0,0 +1,69 @@
package com.minelittlepony.unicopia.container;
import java.util.*;
import java.util.concurrent.*;
import java.util.stream.Collectors;
import com.google.gson.*;
import com.minelittlepony.unicopia.Unicopia;
import com.minelittlepony.unicopia.container.SpellbookChapterList.*;
import com.minelittlepony.unicopia.util.Resources;
import net.fabricmc.fabric.api.resource.IdentifiableResourceReloadListener;
import net.minecraft.client.MinecraftClient;
import net.minecraft.resource.JsonDataLoader;
import net.minecraft.resource.ResourceManager;
import net.minecraft.util.*;
import net.minecraft.util.profiler.Profiler;
public class SpellbookChapterLoader extends JsonDataLoader implements IdentifiableResourceReloadListener {
public static boolean DEBUG = true;
private static final Identifier ID = Unicopia.id("spellbook/chapters");
public static final SpellbookChapterLoader INSTANCE = new SpellbookChapterLoader();
private Map<Identifier, Chapter> chapters = new HashMap<>();
public SpellbookChapterLoader() {
super(Resources.GSON, ID.getPath());
}
@Override
public Identifier getFabricId() {
return ID;
}
public Set<SpellbookChapterList.Chapter> getChapters() {
return new HashSet<>(chapters.values());
}
private static final Executor EXECUTOR = CompletableFuture.delayedExecutor(5, TimeUnit.SECONDS);
@Override
protected void apply(Map<Identifier, JsonElement> data, ResourceManager manager, Profiler profiler) {
chapters = data.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, entry -> {
JsonObject json = JsonHelper.asObject(entry.getValue(), "root");
return new Chapter(entry.getKey(),
TabSide.valueOf(JsonHelper.getString(json, "side")),
JsonHelper.getInt(json, "y_position"),
JsonHelper.getInt(json, "color", 0),
loadContent(JsonHelper.getObject(json, "content", new JsonObject()))
);
}));
if (DEBUG) {
CompletableFuture.runAsync(() -> {
reload(CompletableFuture::completedFuture, manager, profiler, profiler, Util.getMainWorkerExecutor(), MinecraftClient.getInstance());
}, EXECUTOR);
}
}
private Optional<Content> loadContent(JsonObject json) {
return Optional.of(JsonHelper.getArray(json, "pages", new JsonArray()))
.filter(pages -> pages.size() > 0)
.map(DynamicContent::new);
}
}

View file

@ -0,0 +1,141 @@
package com.minelittlepony.unicopia.container;
import com.minelittlepony.common.client.gui.IViewRoot;
import com.minelittlepony.common.client.gui.ScrollContainer;
import com.minelittlepony.common.client.gui.element.Label;
import com.minelittlepony.unicopia.ability.magic.spell.crafting.SpellbookRecipe;
import com.minelittlepony.unicopia.ability.magic.spell.trait.Trait;
import com.minelittlepony.unicopia.container.SpellbookScreen.TraitButton;
import com.minelittlepony.unicopia.item.URecipes;
import com.mojang.blaze3d.systems.RenderSystem;
import net.minecraft.client.gui.screen.recipebook.RecipeBookProvider;
import net.minecraft.client.gui.screen.recipebook.RecipeBookWidget;
import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.text.Text;
public class SpellbookCraftingPageContent extends ScrollContainer implements SpellbookChapterList.Content, RecipeBookProvider {
private final SpellbookScreen screen;
private final RecipeBookWidget recipeBook = new RecipeBookWidget();
public SpellbookCraftingPageContent(SpellbookScreen screen) {
this.screen = screen;
backgroundColor = 0xFFf9efd3;
scrollbar.layoutToEnd = true;
}
@Override
public void init(SpellbookScreen screen) {
screen.addPageButtons(187, 300, 350, SpellbookPage::swap);
refreshRecipeBook();
screen.addDrawable(this);
((IViewRoot)screen).getChildElements().add(this);
}
@Override
public void draw(MatrixStack matrices, int mouseX, int mouseY) {
textRenderer.draw(matrices, screen.getTitle(), SpellbookScreen.TITLE_X, SpellbookScreen.TITLE_Y, SpellbookScreen.TITLE_COLOR);
textRenderer.draw(matrices, SpellbookPage.getCurrent().getLabel(), screen.getBackgroundWidth() / 2 + SpellbookScreen.TITLE_X, SpellbookScreen.TITLE_Y, SpellbookScreen.TITLE_COLOR);
Text pageText = Text.translatable("%s/%s", SpellbookPage.getCurrent().ordinal() + 1, SpellbookPage.VALUES.length);
textRenderer.draw(matrices, pageText, 337 - textRenderer.getWidth(pageText) / 2F, 190, SpellbookScreen.TITLE_COLOR);
}
@Override
public void refreshRecipeBook() {
init(this::initPageContent);
}
@Override
public RecipeBookWidget getRecipeBookWidget() {
return recipeBook;
}
private void initPageContent() {
getContentPadding().setVertical(10);
getContentPadding().bottom = 30;
switch (SpellbookPage.getCurrent()) {
case DISCOVERIES: {
int i = 0;
int cols = 4;
int top = 10;
int left = 25;
for (Trait trait : Trait.all()) {
int x = i % cols;
int y = i / cols;
addButton(new TraitButton(left + x * 32, top + y * 32, trait));
i++;
}
break;
}
case INVENTORY:
// handled elsewhere
break;
case RECIPES:
int top = 0;
for (SpellbookRecipe recipe : this.client.world.getRecipeManager().listAllOfType(URecipes.SPELLBOOK)) {
if (client.player.getRecipeBook().contains(recipe)) {
IngredientTree tree = new IngredientTree(0, top,
width - scrollbar.getBounds().width + 2,
20);
recipe.buildCraftingTree(tree);
top += tree.build(this);
}
}
if (top == 0) {
addButton(new Label(width / 2, 0).setCentered()).getStyle().setText("gui.unicopia.spellbook.page.recipes.empty");
}
}
}
@Override
public void init(Runnable contentInitializer) {
if (screen != null) {
margin.left = screen.getX() + screen.getBackgroundWidth() / 2 + 10;
margin.top = screen.getY() + 35;
margin.right = screen.width - screen.getBackgroundWidth() - screen.getX() + 30;
margin.bottom = screen.height - screen.getBackgroundHeight() - screen.getY() + 40;
}
super.init(contentInitializer);
}
@Override
public void drawOverlays(MatrixStack matrices, int mouseX, int mouseY, float tickDelta) {
matrices.push();
matrices.translate(margin.left, margin.top, 0);
matrices.translate(-2, -2, 0);
RenderSystem.enableBlend();
RenderSystem.setShaderTexture(0, SpellbookScreen.TEXTURE);
int tileSize = 25;
final int bottom = height - tileSize + 4;
final int right = width - tileSize + 9;
drawTexture(matrices, 0, 0, 405, 62, tileSize, tileSize, 512, 256);
drawTexture(matrices, right, 0, 425, 62, tileSize, tileSize, 512, 256);
drawTexture(matrices, 0, bottom, 405, 72, tileSize, tileSize, 512, 256);
drawTexture(matrices, right, bottom, 425, 72, tileSize, tileSize, 512, 256);
for (int i = tileSize; i < right; i += tileSize) {
drawTexture(matrices, i, 0, 415, 62, tileSize, tileSize, 512, 256);
drawTexture(matrices, i, bottom, 415, 72, tileSize, tileSize, 512, 256);
}
for (int i = tileSize; i < bottom; i += tileSize) {
drawTexture(matrices, 0, i, 405, 67, tileSize, tileSize, 512, 256);
drawTexture(matrices, right, i, 425, 67, tileSize, tileSize, 512, 256);
}
matrices.pop();
screen.drawSlots(matrices, mouseX, mouseY, tickDelta);
super.drawOverlays(matrices, mouseX, mouseY, tickDelta);
}
}

View file

@ -17,14 +17,6 @@ public enum SpellbookPage {
return label;
}
public boolean isFirst() {
return ordinal() == 0;
}
public boolean isLast() {
return ordinal() == VALUES.length - 1;
}
public static SpellbookPage getCurrent() {
return VALUES[current];
}

View file

@ -0,0 +1,115 @@
package com.minelittlepony.unicopia.container;
import com.minelittlepony.common.client.gui.dimension.Bounds;
import com.minelittlepony.unicopia.Unicopia;
import com.minelittlepony.unicopia.client.gui.DrawableUtil;
import com.minelittlepony.unicopia.entity.player.MagicReserves;
import com.minelittlepony.unicopia.entity.player.Pony;
import com.mojang.blaze3d.systems.RenderSystem;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.font.TextRenderer;
import net.minecraft.client.gui.DrawableHelper;
import net.minecraft.client.resource.language.I18n;
import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.text.Text;
import net.minecraft.util.math.MathHelper;
public class SpellbookProfilePageContent extends DrawableHelper implements SpellbookChapterList.Content {
private final MinecraftClient client = MinecraftClient.getInstance();
private final Pony pony = Pony.of(client.player);
private final TextRenderer font = client.textRenderer;
private final SpellbookScreen screen;
public SpellbookProfilePageContent(SpellbookScreen screen) {
this.screen = screen;
}
@Override
public void init(SpellbookScreen screen) {
}
@Override
public void draw(MatrixStack matrices, int mouseX, int mouseY) {
int y = SpellbookScreen.TITLE_Y;
float delta = pony.getEntity().age + client.getTickDelta();
int currentLevel = pony.getLevel().get();
matrices.push();
matrices.translate(SpellbookScreen.TITLE_X, y, 0);
matrices.scale(1.3F, 1.3F, 1);
font.draw(matrices, pony.getEntity().getName(), 0, 0, SpellbookScreen.TITLE_COLOR);
matrices.pop();
matrices.push();
matrices.translate(SpellbookScreen.TITLE_X, y + 13, 0);
matrices.scale(0.8F, 0.8F, 1);
font.draw(matrices, "Friendship Student", 0, 0, 0xAA0040FF);
matrices.pop();
MagicReserves reserves = pony.getMagicalReserves();
matrices.push();
matrices.translate(screen.getBackgroundWidth() / 2 + SpellbookScreen.TITLE_X - 10, y, 0);
matrices.scale(1.3F, 1.3F, 1);
font.draw(matrices, SpellbookPage.INVENTORY.getLabel(), 0, 0, SpellbookScreen.TITLE_COLOR);
matrices.pop();
Bounds bounds = screen.getFrameBounds();
matrices.push();
matrices.translate(bounds.left + bounds.width / 4 - 10, bounds.top + bounds.height / 2 + 20, 0);
double growth = MathHelper.sin(delta / 9F) * 2;
double radius = 40 + growth;
float xpPercentage = reserves.getXp().getPercentFill();
float manaPercentage = reserves.getMana().getPercentFill();
float alphaF = (MathHelper.sin(delta / 9F) + 1) / 2F;
int alpha = (int)(alphaF * 0x10) & 0xFF;
int color = 0x10404000 | alpha;
int xpColor = 0xAA0040FF | ((int)(xpPercentage * 0xFF) & 0xFF) << 16;
DrawableUtil.drawArc(matrices, 0, radius + 24, 0, DrawableUtil.TAU, color, false);
DrawableUtil.drawArc(matrices, radius / 3, radius + 6, 0, DrawableUtil.TAU, color, false);
DrawableUtil.drawArc(matrices, radius / 3, radius + 6, 0, xpPercentage * DrawableUtil.TAU, xpColor, false);
radius += 8;
DrawableUtil.drawArc(matrices, radius, radius + 6 + growth, 0, manaPercentage * DrawableUtil.TAU, 0xFF40F040, false);
String manaString = (int)reserves.getMana().get() + "/" + (int)reserves.getMana().getMax();
y = 15;
font.draw(matrices, "Mana", -font.getWidth("Mana") / 2, y, SpellbookScreen.TITLE_COLOR);
font.draw(matrices, manaString, -font.getWidth(manaString) / 2, y += font.fontHeight, SpellbookScreen.TITLE_COLOR);
matrices.push();
matrices.scale(1.4F, 1.4F, 1);
RenderSystem.setShaderTexture(0, Unicopia.id("textures/gui/icons.png"));
drawTexture(matrices, -8, -8, 16 * 2, 0, 16, 16, 256, 256);
RenderSystem.setShaderTexture(0, SpellbookScreen.TEXTURE);
matrices.pop();
Text levelString = I18n.hasTranslation("enchantment.level." + (currentLevel + 1)) ? Text.translatable("enchantment.level." + (currentLevel + 1)) : Text.literal(currentLevel >= 999 ? ">999" : "" + (currentLevel + 1));
matrices.translate(-font.getWidth(levelString), -35, 0);
matrices.scale(2F, 2F, 1);
font.draw(matrices, levelString, 0, 0, SpellbookScreen.TITLE_COLOR);
matrices.pop();
matrices.push();
matrices.translate(-screen.getX(), -screen.getY(), 0);
screen.drawSlots(matrices, mouseX, mouseY, 0);
matrices.pop();
}
static void drawBar(MatrixStack matrices, int x, int y, float value, int color) {
int barWidth = 40;
int midpoint = x + (int)(barWidth * value);
fill(matrices, x, y, midpoint, y + 5, 0xFFAAFFFF);
fill(matrices, midpoint, y, x + barWidth, y + 5, color);
}
}

View file

@ -1,28 +1,32 @@
package com.minelittlepony.unicopia.container;
import com.minelittlepony.common.client.gui.IViewRoot;
import com.minelittlepony.common.client.gui.ScrollContainer;
import java.util.Optional;
import java.util.function.IntConsumer;
import com.minelittlepony.common.client.gui.GameGui;
import com.minelittlepony.common.client.gui.dimension.Bounds;
import com.minelittlepony.common.client.gui.element.Button;
import com.minelittlepony.common.client.gui.element.Label;
import com.minelittlepony.common.client.gui.sprite.TextureSprite;
import com.minelittlepony.unicopia.Unicopia;
import com.minelittlepony.unicopia.ability.magic.spell.crafting.SpellbookRecipe;
import com.minelittlepony.unicopia.ability.magic.spell.trait.Trait;
import com.minelittlepony.unicopia.ability.magic.spell.trait.TraitDiscovery;
import com.minelittlepony.unicopia.container.SpellbookScreenHandler.InputSlot;
import com.minelittlepony.unicopia.container.SpellbookScreenHandler.SpellbookSlot;
import com.minelittlepony.unicopia.container.SpellbookChapterList.*;
import com.minelittlepony.unicopia.container.SpellbookScreenHandler.*;
import com.minelittlepony.unicopia.entity.player.Pony;
import com.minelittlepony.unicopia.item.URecipes;
import com.mojang.blaze3d.platform.GlStateManager;
import com.mojang.blaze3d.systems.RenderSystem;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.gui.Drawable;
import net.minecraft.client.gui.screen.ingame.HandledScreen;
import net.minecraft.client.gui.screen.recipebook.RecipeBookProvider;
import net.minecraft.client.gui.screen.recipebook.RecipeBookWidget;
import net.minecraft.client.render.GameRenderer;
import net.minecraft.client.texture.NativeImage;
import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.entity.player.PlayerInventory;
import net.minecraft.screen.slot.Slot;
import net.minecraft.sound.SoundEvents;
import net.minecraft.text.Text;
import net.minecraft.util.Identifier;
@ -31,82 +35,82 @@ public class SpellbookScreen extends HandledScreen<SpellbookScreenHandler> imple
public static final Identifier SLOT = Unicopia.id("textures/gui/container/slot.png");
public static final Identifier GEM = Unicopia.id("textures/item/gemstone.png");
private final RecipeBookWidget recipeBook = new RecipeBookWidget();
private static final int CONTENT_PADDING = 30;
private final ScrollContainer container = new ScrollContainer() {
{
backgroundColor = 0xFFf9efd3;
scrollbar.layoutToEnd = true;
}
@Override
public void init(Runnable contentInitializer) {
margin.left = SpellbookScreen.this.x + backgroundWidth / 2 + 10;
margin.top = SpellbookScreen.this.y + 35;
margin.right = SpellbookScreen.this.width - backgroundWidth - SpellbookScreen.this.x + 30;
margin.bottom = SpellbookScreen.this.height - backgroundHeight - SpellbookScreen.this.y + 40;
super.init(contentInitializer);
}
public static final int TITLE_X = 30;
public static final int TITLE_Y = 20;
public static final int TITLE_COLOR = 0xFF404040;
@Override
public void drawOverlays(MatrixStack matrices, int mouseX, int mouseY, float tickDelta) {
matrices.push();
matrices.translate(margin.left, margin.top, 0);
matrices.translate(-2, -2, 0);
RenderSystem.enableBlend();
RenderSystem.setShaderTexture(0, TEXTURE);
int tileSize = 25;
private final SpellbookCraftingPageContent craftingPageWidget = new SpellbookCraftingPageContent(this);
final int bottom = height - tileSize + 4;
final int right = width - tileSize + 9;
private final Chapter craftingChapter = new Chapter(SpellbookChapterList.CRAFTING_ID, TabSide.LEFT, 0, 0, Optional.of(craftingPageWidget));
private final Chapter profileChapter = new Chapter(SpellbookChapterList.PROFILE_ID, TabSide.LEFT, 1, 0, Optional.of(new SpellbookProfilePageContent(this)));
drawTexture(matrices, 0, 0, 405, 62, tileSize, tileSize, 512, 256);
drawTexture(matrices, right, 0, 425, 62, tileSize, tileSize, 512, 256);
private final SpellbookChapterList chapters = new SpellbookChapterList(craftingChapter, profileChapter);
private final SpellbookTabBar tabs = new SpellbookTabBar(this, chapters);
drawTexture(matrices, 0, bottom, 405, 72, tileSize, tileSize, 512, 256);
drawTexture(matrices, right, bottom, 425, 72, tileSize, tileSize, 512, 256);
for (int i = tileSize; i < right; i += tileSize) {
drawTexture(matrices, i, 0, 415, 62, tileSize, tileSize, 512, 256);
drawTexture(matrices, i, bottom, 415, 72, tileSize, tileSize, 512, 256);
}
for (int i = tileSize; i < bottom; i += tileSize) {
drawTexture(matrices, 0, i, 405, 67, tileSize, tileSize, 512, 256);
drawTexture(matrices, right, i, 425, 67, tileSize, tileSize, 512, 256);
}
matrices.pop();
drawSlots(matrices, mouseX, mouseY, tickDelta);
super.drawOverlays(matrices, mouseX, mouseY, tickDelta);
}
};
private Bounds contentBounds = Bounds.empty();
public SpellbookScreen(SpellbookScreenHandler handler, PlayerInventory inventory, Text title) {
super(handler, inventory, title);
backgroundWidth = 405;
backgroundHeight = 219;
titleX = 30;
titleY = 20;
contentBounds = new Bounds(CONTENT_PADDING, CONTENT_PADDING, backgroundWidth - CONTENT_PADDING * 2, backgroundHeight - CONTENT_PADDING * 3 - 2);
handler.addSlotShowingCondition(slotType -> {
if (slotType == SlotType.INVENTORY) {
return chapters.getCurrentChapter() == profileChapter
|| (chapters.getCurrentChapter() == craftingChapter && SpellbookPage.getCurrent() == SpellbookPage.INVENTORY);
}
return chapters.getCurrentChapter() == craftingChapter;
});
}
public void addPageButtons(int buttonY, int prevX, int nextX, IntConsumer pageAction) {
addDrawableChild(new PageButton(this, x + nextX, y + buttonY, 1, pageAction));
addDrawableChild(new PageButton(this, x + prevX, y + buttonY, -1, pageAction));
}
public int getX() {
return x;
}
public int getY() {
return y;
}
public int getBackgroundWidth() {
return backgroundWidth;
}
public int getBackgroundHeight() {
return backgroundHeight;
}
public Bounds getFrameBounds() {
return contentBounds;
}
@Override
public <T extends Drawable> T addDrawable(T drawable) {
return super.addDrawable(drawable);
}
@Override
public void init() {
super.init();
addDrawableChild(new PageButton(x + 350, y + 187, 1));
addDrawableChild(new PageButton(x + 300, y + 187, -1));
container.init(this::initPageContent);
addDrawable(container);
((IViewRoot)this).getChildElements().add(container);
tabs.init();
chapters.getCurrentChapter().content().ifPresent(content -> content.init(this));
}
@Override
public void refreshRecipeBook() {
container.init(this::initPageContent);
craftingPageWidget.refreshRecipeBook();
}
@Override
public RecipeBookWidget getRecipeBookWidget() {
return recipeBook;
return craftingPageWidget.getRecipeBookWidget();
}
@Override
@ -121,13 +125,34 @@ public class SpellbookScreen extends HandledScreen<SpellbookScreenHandler> imple
RenderSystem.setShaderColor(1, 1, 1, 1);
RenderSystem.setShaderTexture(0, TEXTURE);
int left = (width - backgroundWidth) / 2;
int top = (height - backgroundHeight) / 2;
drawTexture(matrices, x, y, 0, 0, backgroundWidth, backgroundHeight, 512, 256);
drawTexture(matrices, left, top, 0, 0, backgroundWidth, backgroundHeight, 512, 256);
tabs.getAllTabs().forEach(tab -> {
Bounds bounds = tab.bounds();
chapters.getCurrentChapter();
boolean hover = bounds.contains(mouseX, mouseY);
int color = tab.chapter().color() & 0xFFFFFF;
int v = 100 + (hover ? 24 : 0);
if (color == 0xFFFFFF || color == 0) {
v += 48;
} else {
RenderSystem.setShaderColor(NativeImage.getRed(color), NativeImage.getGreen(color), NativeImage.getBlue(color), 1);
}
protected void drawSlots(MatrixStack matrices, int mouseX, int mouseY, float delta) {
boolean isRight = tab.chapter().side() == TabSide.RIGHT;
drawTexture(matrices, bounds.left, bounds.top, isRight ? 510 - bounds.width : 402, v, bounds.width, bounds.height, 512, 256);
RenderSystem.setShaderColor(1, 1, 1, 1);
RenderSystem.setShaderTexture(0, tab.icon());
drawTexture(matrices, isRight ? bounds.left + bounds.width - 16 - 10 : bounds.left + 10, bounds.top + (bounds.height - 16) / 2, 0, 0, 16, 16, 16, 16);
RenderSystem.setShaderTexture(0, TEXTURE);
});
}
void drawSlots(MatrixStack matrices, int mouseX, int mouseY, float delta) {
matrices.push();
matrices.translate(x, y, 0);
RenderSystem.setShaderColor(1, 1, 1, 1);
@ -154,53 +179,22 @@ public class SpellbookScreen extends HandledScreen<SpellbookScreenHandler> imple
@Override
protected void drawForeground(MatrixStack matrices, int mouseX, int mouseY) {
textRenderer.draw(matrices, title, titleX, titleY, 4210752);
textRenderer.draw(matrices, SpellbookPage.getCurrent().getLabel(), 220, this.titleY, 4210752);
Text pageText = Text.translatable("%s/%s", SpellbookPage.getCurrent().ordinal() + 1, SpellbookPage.VALUES.length);
textRenderer.draw(matrices, pageText,
x + 325 - textRenderer.getWidth(pageText) / 2F, y + 188 - textRenderer.fontHeight, 4210752);
chapters.getCurrentChapter().content().ifPresent(content -> content.draw(matrices, mouseX, mouseY));
}
private void initPageContent() {
container.getContentPadding().setVertical(10);
container.getContentPadding().bottom = 30;
@Override
public boolean mouseClicked(double mouseX, double mouseY, int button) {
return tabs.getAllTabs().anyMatch(tab -> {
switch (SpellbookPage.getCurrent()) {
case DISCOVERIES: {
int i = 0;
int cols = 4;
if (tab.bounds().contains(mouseX, mouseY) && chapters.getCurrentChapter() != tab.chapter()) {
chapters.setCurrentChapter(tab.chapter());
GameGui.playSound(SoundEvents.ITEM_BOOK_PAGE_TURN);
clearAndInit();
return true;
}
int top = 10;
int left = 25;
for (Trait trait : Trait.all()) {
int x = i % cols;
int y = i / cols;
container.addButton(new TraitButton(left + x * 32, top + y * 32, trait));
i++;
}
break;
}
case INVENTORY:
// handled elsewhere
break;
case RECIPES:
int top = 0;
for (SpellbookRecipe recipe : this.client.world.getRecipeManager().listAllOfType(URecipes.SPELLBOOK)) {
if (client.player.getRecipeBook().contains(recipe)) {
IngredientTree tree = new IngredientTree(0, top,
container.width - container.scrollbar.getBounds().width + 2,
20);
recipe.buildCraftingTree(tree);
top += tree.build(container);
}
}
if (top == 0) {
container.addButton(new Label(container.width / 2, 0).setCentered()).getStyle().setText("gui.unicopia.spellbook.page.recipes.empty");
}
}
return false;
}) || super.mouseClicked(mouseX, mouseY, button);
}
@Override
@ -217,7 +211,7 @@ public class SpellbookScreen extends HandledScreen<SpellbookScreenHandler> imple
|| super.mouseDragged(mouseX, mouseY, button, deltaX, deltaY);
}
class PageButton extends ImageButton {
private static class PageButton extends ImageButton {
private final int increment;
private final TextureSprite sprite = new TextureSprite()
.setSize(25, 13)
@ -225,21 +219,18 @@ public class SpellbookScreen extends HandledScreen<SpellbookScreenHandler> imple
.setTextureOffset(0, 479)
.setTexture(TEXTURE);
public PageButton(int x, int y, int increment) {
public PageButton(SpellbookScreen screen, int x, int y, int increment, IntConsumer pageAction) {
super(x, y, 25, 20);
this.increment = increment;
getStyle().setIcon(sprite);
onClick(sender -> {
SpellbookPage.swap(increment);
init(client, SpellbookScreen.this.width, SpellbookScreen.this.height);
pageAction.accept(increment);
screen.clearAndInit();
});
}
@Override
public void renderButton(MatrixStack matrices, int mouseX, int mouseY, float tickDelta) {
setEnabled(increment < 0 ? !SpellbookPage.getCurrent().isFirst() : !SpellbookPage.getCurrent().isLast());
if (!active) {
return;
}
@ -251,7 +242,7 @@ public class SpellbookScreen extends HandledScreen<SpellbookScreenHandler> imple
}
}
class TraitButton extends ImageButton {
static class TraitButton extends ImageButton {
private final Trait trait;
public TraitButton(int x, int y, Trait trait) {
@ -263,12 +254,12 @@ public class SpellbookScreen extends HandledScreen<SpellbookScreenHandler> imple
.setTexture(trait.getSprite()));
getStyle().setTooltip(trait.getTooltip());
onClick(sender -> Pony.of(client.player).getDiscoveries().markRead(trait));
onClick(sender -> Pony.of(MinecraftClient.getInstance().player).getDiscoveries().markRead(trait));
}
@Override
public void renderButton(MatrixStack matrices, int mouseX, int mouseY, float tickDelta) {
TraitDiscovery discoveries = Pony.of(client.player).getDiscoveries();
TraitDiscovery discoveries = Pony.of(MinecraftClient.getInstance().player).getDiscoveries();
setEnabled(discoveries.isKnown(trait));
RenderSystem.setShaderColor(1, 1, 1, 1);
@ -295,7 +286,7 @@ public class SpellbookScreen extends HandledScreen<SpellbookScreenHandler> imple
}
}
class ImageButton extends Button {
static class ImageButton extends Button {
public ImageButton(int x, int y) {
super(x, y);

View file

@ -1,6 +1,7 @@
package com.minelittlepony.unicopia.container;
import java.util.*;
import java.util.function.Predicate;
import com.minelittlepony.unicopia.EquinePredicates;
import com.minelittlepony.unicopia.USounds;
@ -56,6 +57,8 @@ public class SpellbookScreenHandler extends ScreenHandler {
private final ScreenHandlerContext context;
private Predicate<SlotType> canShowSlots;
protected SpellbookScreenHandler(int syncId, PlayerInventory inv) {
this(syncId, inv, ScreenHandlerContext.EMPTY);
}
@ -133,6 +136,10 @@ public class SpellbookScreenHandler extends ScreenHandler {
onContentChanged(input);
}
public void addSlotShowingCondition(Predicate<SlotType> canShowSlots) {
this.canShowSlots = canShowSlots;
}
public int getOutputSlotId() {
return outputSlot.id;
}
@ -154,7 +161,10 @@ public class SpellbookScreenHandler extends ScreenHandler {
.findFirst()
.filter(recipe -> result.shouldCraftRecipe(world, (ServerPlayerEntity)this.inventory.player, recipe))
.map(recipe -> recipe.craft(input))
.orElse(!input.hasIngredients() ? ItemStack.EMPTY : input.getTraits().applyTo(UItems.BOTCHED_GEM.getDefaultStack())));
.orElse(!input.hasIngredients() || input.getItemToModify().getItem() != UItems.GEMSTONE
? ItemStack.EMPTY
: input.getTraits().applyTo(UItems.BOTCHED_GEM.getDefaultStack()))
);
((ServerPlayerEntity)this.inventory.player).networkHandler.sendPacket(new ScreenHandlerSlotUpdateS2CPacket(syncId, nextRevision(), outputSlot.id, outputSlot.getStack()));
}
@ -363,7 +373,7 @@ public class SpellbookScreenHandler extends ScreenHandler {
}
}
public static class InventorySlot extends Slot implements SpellbookSlot {
public class InventorySlot extends Slot implements SpellbookSlot {
public InventorySlot(Inventory inventory, int index, int x, int y) {
super(inventory, index, x, y);
}
@ -375,11 +385,11 @@ public class SpellbookScreenHandler extends ScreenHandler {
@Override
public boolean isEnabled() {
return SpellbookPage.getCurrent() == SpellbookPage.INVENTORY;
return canShowSlots.test(SlotType.INVENTORY);
}
}
public static class ModifierSlot extends Slot implements SpellbookSlot {
public class ModifierSlot extends Slot implements SpellbookSlot {
private final int ring;
public ModifierSlot(Inventory inventory, int index, int[] params) {
@ -401,6 +411,11 @@ public class SpellbookScreenHandler extends ScreenHandler {
public int getRing() {
return ring;
}
@Override
public boolean isEnabled() {
return canShowSlots.test(SlotType.CRAFTING) && super.isEnabled();
}
}
public class InputSlot extends Slot implements SpellbookSlot {
@ -423,7 +438,7 @@ public class SpellbookScreenHandler extends ScreenHandler {
@Override
public boolean isEnabled() {
return !outputSlot.isEnabled();
return canShowSlots.test(SlotType.CRAFTING) && !outputSlot.isEnabled();
}
}
@ -457,7 +472,7 @@ public class SpellbookScreenHandler extends ScreenHandler {
@Override
public boolean isEnabled() {
return hasStack();
return canShowSlots.test(SlotType.CRAFTING) && hasStack();
}
@Override
@ -470,4 +485,9 @@ public class SpellbookScreenHandler extends ScreenHandler {
super.onTakeItem(player, stack);
}
}
public enum SlotType {
INVENTORY,
CRAFTING
}
}

View file

@ -0,0 +1,71 @@
package com.minelittlepony.unicopia.container;
import java.util.*;
import java.util.stream.Stream;
import com.minelittlepony.common.client.gui.dimension.Bounds;
import com.minelittlepony.unicopia.container.SpellbookChapterList.Chapter;
import com.minelittlepony.unicopia.container.SpellbookChapterList.TabSide;
import net.minecraft.util.Identifier;
public class SpellbookTabBar {
private final SpellbookScreen screen;
private final SpellbookChapterList chapters;
private final Map<TabSide, List<Tab>> bars = new EnumMap<>(TabSide.class);
public SpellbookTabBar(SpellbookScreen screen, SpellbookChapterList chapters) {
this.screen = screen;
this.chapters = chapters;
}
public void init() {
bars.clear();
Stream.of(TabSide.values()).forEach(side -> {
bars.put(side, buildTabList(side));
});
}
public Stream<Tab> getAllTabs() {
return bars.values().stream().flatMap(l -> l.stream());
}
private List<Tab> buildTabList(TabSide side) {
List<Tab> tabs = new ArrayList<>();
int backgroundHeight = screen.getBackgroundHeight();
int backgroundWidth = screen.getBackgroundWidth();
int tabInset = 14;
int top = (screen.height - backgroundHeight) / 2 + 20;
int left = (screen.width - backgroundWidth) / 2;
List<Chapter> leftTabs = chapters.getTabs(side)
.sorted(Comparator.comparing(Chapter::tabY))
.toList();
int tabWidth = 35;
int tabHeight = 23;
int totalTabs = leftTabs.size();
float squashFactor = Math.min(1, (float)(backgroundHeight - 40) / ((tabHeight + 8) * leftTabs.size()));
for (int i = 0; i < totalTabs; i++) {
int tabY = (int)((leftTabs.get(i).tabY() * tabHeight + (i * 5)) * squashFactor);
int width = tabWidth;
if (leftTabs.get(i) == chapters.getCurrentChapter()) {
width += 3;
}
int xPosition = side == TabSide.LEFT ? left - width + tabInset : left + backgroundWidth - tabInset;
tabs.add(new Tab(leftTabs.get(i), new Bounds(top + tabY, xPosition, width, tabHeight), leftTabs.get(i).icon()));
}
return tabs;
}
record Tab (Chapter chapter, Bounds bounds, Identifier icon) {}
}

View file

@ -0,0 +1,5 @@
{
"side": "RIGHT",
"y_position": 4,
"color": 0
}

View file

@ -0,0 +1,5 @@
{
"side": "RIGHT",
"y_position": 7,
"color": 0
}

View file

@ -0,0 +1,5 @@
{
"side": "RIGHT",
"y_position": 5,
"color": 0
}

View file

@ -0,0 +1,5 @@
{
"side": "RIGHT",
"y_position": 3,
"color": 0
}

View file

@ -0,0 +1,5 @@
{
"side": "RIGHT",
"y_position": 2,
"color": 0
}

View file

@ -0,0 +1,113 @@
{
"side": "RIGHT",
"y_position": 0,
"color": 0,
"content": {
"pages": [
{
"title": "Preface",
"level": 0,
"elements": [
{
"text": "To whomever holds this tome, beware what you seek for you might not like what you find.",
"extra": [
{ "text": "Hither yonder equs.", "obfuscated": true }
]
},
"At the princess' behest",
"- Starswirl the Bearded"
]
},
{
"title": "Magic in Equestria",
"level": 0,
"elements": [
{
"text": "Equestria is filled with magic of all different shapes and forms. Following recent events, however, it's has become plainly obvious that we do not fully understand all that there is about the world of Equestria. That is why the crown has tasked me with researching Magic in all of its forms, so we might utilise it and, I hope, save ourselves from the",
"extra": [
{ "text": "aaaaaaa", "obfuscated": "true" }
]
},
{ "x": 125, "y": 0, "width": 32, "height": 32, "texture": "unicopia:textures/gui/container/pages/profile.png" }
]
},
{
"title": "1st Mare '12",
"level": 0,
"elements": [
"Odd Rocks",
"These 'Gemstones' are a common material found around the world. Earth Ponies dig them up all the time and don't think any much of it, but these stones have a special trait I found that lets them hold magic.",
"What's odd is that they seem to have some magical properties I haven't seen before.",
{ "x": 125, "y": 0, "width": 32, "height": 32, "texture": "unicopia:textures/item/gemstone.png" }
]
},
{
"title": "Gemstones",
"level": 0,
"elements": [
{
"text": "My research is",
"extra": [
{ "text": "still incomplete", "obfuscated": true},
{ "text": "but I believe these may become useful for storing and applying complex spells, if I do so correctly..."}
]
},
{
"text": "Luna-wants",
"strikethrough": true,
"extra": [
{ "text": "I'm going to keep experimenting. Hooves-crossed, I'll update you tomorrow if I find anything.", "strikethrough": false}
]
},
"At the princess' behest",
"- Starswirl the Bearded"
]
},
{
"title": "2nd Mare '12",
"level": 0,
"elements": [
{
"text": "It worked! Holy",
"extra": [
{ "text": "Celestia's ass-cheeks'", "obfuscated": true},
{ "text": "it actually worked." }
]
},
"This is amazing! These can do so much more than I could have ever imagined. Think of the advances I could bring to Equestria. Gem-powered lighting, heating, cooling, I'd no longer have to spend summer sitting on this-",
"I'm getting ahead of myself. Let me explain..."
]
},
{
"title": "Spellcrafting",
"level": 0,
"elements": [
{ "x": 125, "y": 0, "width": 32, "height": 32, "texture": "unicopia:textures/gui/container/pages/crafting.png" },
"I drew a guide at the start of this book to help with the placement.",
"Put a raw gem-it mustn't have any spells already-in the middle and place materials around it in the slots I marked.",
"Each material gives different effects and putting them closer also enhances their influence on the gem."
]
},
{
"title": "3rd Mare '12",
"level": 0,
"elements": [
"I'm going to start documenting spell combinations as I find them. Some of them are pretty obvious, like gem + fire = fire gem",
"But some are less clear. For instance, what traits would an egg add?",
"At the princess' behest",
"- Starswirl the Bearded"
]
},
{
"title": "Botched Gems",
"level": 1,
"elements": [
{ "x": 127, "y": 1, "width": 32, "height": 32, "texture": "unicopia:textures/item/botched_gem.png" },
"Okay, I guess not every combination works. That's dissapointing, and now I have all these useless stones piling up in my chambers.",
"I don't know what to do with them. They're not edible, I don't think.",
"They do still have the traits I gave them, so maybe I can find a use other than building a rock-fort with little Luna..."
]
}
]
}
}

View file

@ -0,0 +1,5 @@
{
"side": "RIGHT",
"y_position": 6,
"color": 0
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 791 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 789 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 780 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 799 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 791 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 786 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 789 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 785 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 783 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 789 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 821 B