Implement gui for viewing spell recipes

This commit is contained in:
Sollace 2021-12-29 17:50:19 +02:00
parent 3f80822deb
commit 5939ce3d1a
10 changed files with 317 additions and 83 deletions

View file

@ -1,8 +1,11 @@
package com.minelittlepony.unicopia.ability.magic.spell.crafting;
import java.util.Arrays;
import java.util.Optional;
import java.util.function.Predicate;
import org.jetbrains.annotations.Nullable;
import com.google.gson.JsonObject;
import com.minelittlepony.unicopia.ability.magic.spell.effect.SpellType;
import com.minelittlepony.unicopia.item.GemstoneItem;
@ -14,10 +17,12 @@ import net.minecraft.util.Identifier;
import net.minecraft.util.JsonHelper;
public class IngredientWithSpell implements Predicate<ItemStack> {
private Optional<Ingredient> stack = Optional.empty();
private Optional<SpellType<?>> spell = Optional.empty();
@Nullable
private ItemStack[] stacks;
private IngredientWithSpell() {}
@Override
@ -27,6 +32,17 @@ public class IngredientWithSpell implements Predicate<ItemStack> {
return stackMatch && spellMatch;
}
public ItemStack[] getMatchingStacks() {
if (stacks == null) {
stacks = stack.stream()
.map(Ingredient::getMatchingStacks)
.flatMap(Arrays::stream)
.map(stack -> spell.map(spell -> GemstoneItem.enchant(stack, spell)).orElse(stack))
.toArray(ItemStack[]::new);
}
return stacks;
}
public void write(PacketByteBuf buf) {
stack.ifPresentOrElse(i -> {
buf.writeBoolean(true);

View file

@ -1,5 +1,6 @@
package com.minelittlepony.unicopia.ability.magic.spell.crafting;
import com.minelittlepony.unicopia.ability.magic.spell.trait.Trait;
import com.minelittlepony.unicopia.container.SpellbookScreenHandler.SpellbookInventory;
import com.minelittlepony.unicopia.item.UItems;
import com.minelittlepony.unicopia.item.URecipes;
@ -23,4 +24,14 @@ public interface SpellbookRecipe extends Recipe<SpellbookInventory> {
default ItemStack createIcon() {
return new ItemStack(UItems.SPELLBOOK);
}
void buildCraftingTree(CraftingTreeBuilder builder);
interface CraftingTreeBuilder {
void input(ItemStack...stack);
void input(Trait trait, float value);
void result(ItemStack...stack);
}
}

View file

@ -9,12 +9,10 @@ import com.minelittlepony.unicopia.ability.magic.spell.trait.SpellTraits;
import net.minecraft.network.PacketByteBuf;
import net.minecraft.util.JsonHelper;
public class TraitIngredient implements Predicate<SpellTraits> {
private Optional<SpellTraits> min = Optional.empty();
private Optional<SpellTraits> max = Optional.empty();
private TraitIngredient() {}
public record TraitIngredient (
Optional<SpellTraits> min,
Optional<SpellTraits> max
) implements Predicate<SpellTraits> {
@Override
public boolean test(SpellTraits t) {
@ -35,33 +33,26 @@ public class TraitIngredient implements Predicate<SpellTraits> {
}
public static TraitIngredient fromPacket(PacketByteBuf buf) {
TraitIngredient ingredient = new TraitIngredient();
if (buf.readBoolean()) {
ingredient.min = SpellTraits.fromPacket(buf);
}
if (buf.readBoolean()) {
ingredient.max = SpellTraits.fromPacket(buf);
}
return ingredient;
Optional<SpellTraits> min = Optional.empty();
Optional<SpellTraits> max = Optional.empty();
return new TraitIngredient(min, max);
}
public static TraitIngredient fromJson(JsonObject json) {
TraitIngredient ingredient = new TraitIngredient();
Optional<SpellTraits> min = Optional.empty();
Optional<SpellTraits> max = Optional.empty();
if (json.has("min") || json.has("max")) {
if (json.has("min")) {
ingredient.min = SpellTraits.fromJson(JsonHelper.getObject(json, "min"));
min = SpellTraits.fromJson(JsonHelper.getObject(json, "min"));
}
if (json.has("max")) {
ingredient.max = SpellTraits.fromJson(JsonHelper.getObject(json, "max"));
max = SpellTraits.fromJson(JsonHelper.getObject(json, "max"));
}
} else {
ingredient.min = SpellTraits.fromJson(json);
min = SpellTraits.fromJson(json);
}
return ingredient;
return new TraitIngredient(min, max);
}
}

View file

@ -28,11 +28,19 @@ public class TraitRequirementRecipe implements SpellbookRecipe {
this.output = output;
}
@Override
public void buildCraftingTree(CraftingTreeBuilder builder) {
builder.input(requirement.getMatchingStacks());
requiredTraits.min().ifPresent(min -> {
min.forEach(e -> builder.input(e.getKey(), e.getValue()));
});
builder.result(output);
}
@Override
public boolean matches(SpellbookInventory inventory, World world) {
return requirement.test(inventory.getItemToModify())
&& requiredTraits.test(SpellTraits.union(
SpellTraits.of(inventory.getItemToModify()),
inventory.getTraits(),
SpellTraits.of(output)
));

View file

@ -246,6 +246,9 @@ public final class SpellTraits implements Iterable<Map.Entry<Trait, Float>> {
}
static void combine(Map<Trait, Float> to, Map<Trait, Float> from) {
if (from.isEmpty()) {
return;
}
from.forEach((trait, value) -> {
if (value != 0) {
to.compute(trait, (k, v) -> v == null ? value : (v + value));

View file

@ -7,6 +7,12 @@ import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Collectors;
import com.minelittlepony.common.client.gui.Tooltip;
import net.minecraft.item.ItemStack;
import net.minecraft.text.LiteralText;
import net.minecraft.text.TranslatableText;
import net.minecraft.util.Formatting;
import net.minecraft.util.Identifier;
public enum Trait {
@ -74,6 +80,24 @@ public enum Trait {
return sprite;
}
public Tooltip getTooltip() {
Formatting corruptionColor = getGroup().getCorruption() < -0.01F
? Formatting.GREEN
: getGroup().getCorruption() > 0.25F
? Formatting.RED
: Formatting.WHITE;
return Tooltip.of(
new TranslatableText("gui.unicopia.trait.label",
new TranslatableText("trait." + getId().getNamespace() + "." + getId().getPath() + ".name")
).formatted(Formatting.YELLOW)
.append(new TranslatableText("gui.unicopia.trait.group", getGroup().name().toLowerCase()).formatted(Formatting.ITALIC, Formatting.GRAY))
.append(new LiteralText("\n\n").formatted(Formatting.WHITE)
.append(new TranslatableText("trait." + getId().getNamespace() + "." + getId().getPath() + ".description").formatted(Formatting.GRAY))
.append("\n")
.append(new TranslatableText("gui.unicopia.trait.corruption", ItemStack.MODIFIER_FORMAT.format(getGroup().getCorruption())).formatted(Formatting.ITALIC, corruptionColor))), 200);
}
public static Collection<Trait> all() {
return IDS.values();
}

View file

@ -1,11 +1,10 @@
package com.minelittlepony.unicopia.client.gui;
import java.util.Map.Entry;
import com.minelittlepony.unicopia.ability.magic.spell.trait.SpellTraits;
import com.minelittlepony.unicopia.ability.magic.spell.trait.Trait;
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.gui.tooltip.TooltipComponent;
@ -27,7 +26,7 @@ public class ItemTraitsTooltipRenderer extends BaseText implements OrderedText,
@Override
public int getHeight() {
return getRows() * 8 + 4;
return getRows() * 16 + 4;
}
@Override
@ -40,33 +39,20 @@ public class ItemTraitsTooltipRenderer extends BaseText implements OrderedText,
}
private int getRows() {
return (int)Math.ceil((traits.entries().size() + 1) / getColumns());
return Math.max(1, (int)Math.ceil((traits.entries().size() + 1) / getColumns()));
}
@Override
public void drawItems(TextRenderer textRenderer, int x, int y, MatrixStack matrices, ItemRenderer itemRenderer, int z) {
int columns = getColumns();
int i = 0;
var entries = traits.stream().toList();
for (int i = 0; i < entries.size(); i++) {
int xx = x + (i % columns) * 17;
int yy = y + (i / columns) * 10;
Entry<Trait, Float> entry = entries.get(i);
RenderSystem.setShaderTexture(0, entry.getKey().getSprite());
DrawableHelper.drawTexture(matrices, xx, yy, 1, 0, 0, 8, 8, 8, 8);
String string = entry.getValue() > 99 ? "99+" : Math.round(entry.getValue()) + "";
matrices.push();
matrices.translate(xx + 9, yy + 3, itemRenderer.zOffset + 200.0F);
matrices.scale(0.5F, 0.5F, 1);
VertexConsumerProvider.Immediate immediate = VertexConsumerProvider.immediate(Tessellator.getInstance().getBuffer());
textRenderer.draw(string, 0, 0, 16777215, true, matrices.peek().getPositionMatrix(), immediate, false, 0, 15728880);
immediate.draw();
matrices.pop();
for (var entry : traits) {
renderTraitIcon(entry.getKey(), entry.getValue(), matrices,
x + (i % columns) * 17,
y + (i / columns) * 16
);
i++;
}
}
@ -84,4 +70,22 @@ public class ItemTraitsTooltipRenderer extends BaseText implements OrderedText,
public BaseText copy() {
return new ItemTraitsTooltipRenderer(traits);
}
public static void renderTraitIcon(Trait trait, float value, MatrixStack matrices, int xx, int yy) {
TextRenderer textRenderer = MinecraftClient.getInstance().textRenderer;
ItemRenderer itemRenderer = MinecraftClient.getInstance().getItemRenderer();
int size = 12;
RenderSystem.setShaderTexture(0, trait.getSprite());
DrawableHelper.drawTexture(matrices, xx + 2, yy + 1, 0, 0, 0, size, size, size, size);
matrices.push();
matrices.translate(xx + 9, yy + 3 + size / 2, itemRenderer.zOffset + 200.0F);
matrices.scale(0.5F, 0.5F, 1);
VertexConsumerProvider.Immediate immediate = VertexConsumerProvider.immediate(Tessellator.getInstance().getBuffer());
textRenderer.draw(value > 99 ? "99+" : Math.round(value) + "", 0, 0, 16777215, true, matrices.peek().getPositionMatrix(), immediate, false, 0, 15728880);
immediate.draw();
matrices.pop();
}
}

View file

@ -0,0 +1,177 @@
package com.minelittlepony.unicopia.container;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import com.minelittlepony.common.client.gui.ScrollContainer;
import com.minelittlepony.common.client.gui.Tooltip;
import com.minelittlepony.common.client.gui.element.Button;
import com.minelittlepony.unicopia.ability.magic.spell.crafting.SpellbookRecipe;
import com.minelittlepony.unicopia.ability.magic.spell.trait.Trait;
import com.minelittlepony.unicopia.client.gui.ItemTraitsTooltipRenderer;
import com.mojang.blaze3d.systems.RenderSystem;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.item.TooltipContext;
import net.minecraft.client.render.item.ItemRenderer;
import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.item.ItemStack;
import net.minecraft.util.math.Vector4f;
class IngredientTree implements SpellbookRecipe.CraftingTreeBuilder {
private final List<IngredientTree.Entry> entries = new ArrayList<>();
private Optional<IngredientTree.Entry> result = Optional.empty();
private final int x;
private final int y;
private final int width;
public IngredientTree(int x, int y, int width, int height) {
this.x = x + 4;
this.y = y;
this.width = width - 5;
}
@Override
public void input(ItemStack... stacks) {
if (stacks.length > 0) {
entries.add(new Stacks(stacks));
}
}
@Override
public void input(Trait trait, float value) {
if (value != 0) {
entries.add(new Traits(trait, value));
}
}
@Override
public void result(ItemStack...stacks) {
if (stacks.length > 0) {
result = Optional.of(new Stacks(stacks));
}
}
public int build(ScrollContainer container) {
int ii = 0;
int colWidth = 22;
int rowHeight = 20;
int cols = width / colWidth - 1;
int rows = Math.max(1, (int)Math.ceil((float)entries.size() / cols));
int totalHeight = rowHeight * rows;
for (IngredientTree.Entry entry : entries) {
int column = ii % cols;
int row = ii / cols;
int left = x + column * colWidth + 3 + (row > 0 ? colWidth : 0);
int top = y + row * rowHeight + 3;
container.addButton(new IngredientButton(left, top, colWidth, rowHeight, entry, ii == 0 ? "" : "+"));
ii++;
}
result.ifPresent(result -> {
container.addButton(new IngredientButton(x + width - 17, y + totalHeight / 3 - 2, colWidth, totalHeight, result, "="));
});
return totalHeight + 7;
}
class IngredientButton extends Button {
private final IngredientTree.Entry entry;
private String label;
public IngredientButton(int x, int y, int width, int height, IngredientTree.Entry entry, String label) {
super(x, y, width, height);
this.entry = entry;
this.label = label;
this.getStyle().setTooltip(entry.getTooltip());
}
@Override
public void renderButton(MatrixStack matrices, int mouseX, int mouseY, float tickDelta) {
RenderSystem.setShaderColor(1, 1, 1, 1);
RenderSystem.setShaderTexture(0, SpellbookScreen.SLOT);
RenderSystem.enableBlend();
drawTexture(matrices, x - 8, y - 10, 0, 0, 32, 32, 32, 32);
RenderSystem.disableBlend();
RenderSystem.setShaderColor(1, 1, 1, 1);
MinecraftClient.getInstance().textRenderer.draw(matrices, label,
x - MinecraftClient.getInstance().textRenderer.getWidth(label) / 2 - 3,
y + 4,
0
);
entry.render(matrices, x, y, tickDelta);
}
}
interface Entry {
void render(MatrixStack matrices, int mouseX, int mouseY, float tickDelta);
Tooltip getTooltip();
}
class Stacks implements IngredientTree.Entry {
private int ticker;
private int index;
private final ItemStack[] stacks;
private final ItemRenderer itemRenderer = MinecraftClient.getInstance().getItemRenderer();
Stacks(ItemStack[] stacks) {
this.stacks = stacks;
}
@Override
public void render(MatrixStack matrices, int x, int y, float tickDelta) {
y -= 2;
if (ticker++ % 500 == 0) {
index = (index + 1) % stacks.length;
}
float z = itemRenderer.zOffset;
itemRenderer.zOffset = -100;
Vector4f vector4f = new Vector4f(x, y, z, 1);
vector4f.transform(matrices.peek().getPositionMatrix());
itemRenderer.renderInGui(stacks[index], (int)vector4f.getX(), (int)vector4f.getY());
itemRenderer.zOffset = z;
}
@Override
public Tooltip getTooltip() {
return () -> stacks[index].getTooltip(MinecraftClient.getInstance().player, TooltipContext.Default.NORMAL);
}
}
class Traits implements IngredientTree.Entry {
private final Trait trait;
private final float value;
Traits(Trait trait, float value) {
this.trait = trait;
this.value = value;
}
@Override
public void render(MatrixStack matrices, int x, int y, float tickDelta) {
ItemTraitsTooltipRenderer.renderTraitIcon(trait, value, matrices, x, y);
}
@Override
public Tooltip getTooltip() {
return trait.getTooltip();
}
}
}

View file

@ -2,14 +2,15 @@ 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.Tooltip;
import com.minelittlepony.common.client.gui.element.Button;
import com.minelittlepony.common.client.gui.sprite.TextureSprite;
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.OutputSlot;
import com.minelittlepony.unicopia.container.SpellbookScreenHandler.SpellbookSlot;
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;
@ -17,12 +18,9 @@ import net.minecraft.client.gui.screen.ingame.HandledScreen;
import net.minecraft.client.render.GameRenderer;
import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.entity.player.PlayerInventory;
import net.minecraft.item.ItemStack;
import net.minecraft.screen.slot.Slot;
import net.minecraft.text.LiteralText;
import net.minecraft.text.Text;
import net.minecraft.text.TranslatableText;
import net.minecraft.util.Formatting;
import net.minecraft.util.Identifier;
public class SpellbookScreen extends HandledScreen<SpellbookScreenHandler> {
@ -44,7 +42,7 @@ public class SpellbookScreen extends HandledScreen<SpellbookScreenHandler> {
}
@Override
public void drawOverlays(MatrixStack matrices, int mouseX, int mouseY, float partialTicks) {
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);
@ -71,8 +69,9 @@ public class SpellbookScreen extends HandledScreen<SpellbookScreenHandler> {
drawTexture(matrices, right, i, 425, 67, tileSize, tileSize, 512, 256);
}
matrices.pop();
drawSlots(matrices, mouseX, mouseY, partialTicks);
super.drawOverlays(matrices, mouseX, mouseY, partialTicks);
drawSlots(matrices, mouseX, mouseY, tickDelta);
super.drawOverlays(matrices, mouseX, mouseY, tickDelta);
}
};
@ -139,6 +138,10 @@ public class SpellbookScreen extends HandledScreen<SpellbookScreenHandler> {
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 = new TranslatableText("%s/%s", SpellbookPage.getCurrent().ordinal() + 1, SpellbookPage.VALUES.length);
textRenderer.draw(matrices, pageText,
x + 325 - textRenderer.getWidth(pageText) / 2F, y + 188 - textRenderer.fontHeight, 4210752);
}
private void initPageContent() {
@ -149,12 +152,12 @@ public class SpellbookScreen extends HandledScreen<SpellbookScreenHandler> {
switch (SpellbookPage.getCurrent()) {
case DISCOVERIES: {
int top = 10;
int left = 25;
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;
@ -165,7 +168,17 @@ public class SpellbookScreen extends HandledScreen<SpellbookScreenHandler> {
break;
}
case INVENTORY:
// handled elsewhere
break;
case RECIPES:
int top = 0;
for (SpellbookRecipe recipe : this.client.world.getRecipeManager().listAllOfType(URecipes.SPELLBOOK)) {
IngredientTree tree = new IngredientTree(0, top,
container.width - container.scrollbar.getBounds().width + 2,
20);
recipe.buildCraftingTree(tree);
top += tree.build(container);
}
}
}
@ -187,7 +200,7 @@ public class SpellbookScreen extends HandledScreen<SpellbookScreenHandler> {
private final int increment;
private final TextureSprite sprite = new TextureSprite()
.setSize(25, 13)
.setTextureSize(256, 512)
.setTextureSize(512, 256)
.setTextureOffset(0, 479)
.setTexture(TEXTURE);
@ -202,7 +215,7 @@ public class SpellbookScreen extends HandledScreen<SpellbookScreenHandler> {
}
@Override
public void renderButton(MatrixStack matrices, int mouseX, int mouseY, float partialTicks) {
public void renderButton(MatrixStack matrices, int mouseX, int mouseY, float tickDelta) {
setEnabled(increment < 0 ? !SpellbookPage.getCurrent().isFirst() : !SpellbookPage.getCurrent().isLast());
@ -213,7 +226,7 @@ public class SpellbookScreen extends HandledScreen<SpellbookScreenHandler> {
int state = hovered ? 1 : 0;
sprite.setTextureOffset(23 * state, (int)(479 + 6.5F - (increment * 6.5F)));
super.renderButton(matrices, mouseX, mouseY, partialTicks);
super.renderButton(matrices, mouseX, mouseY, tickDelta);
}
}
@ -227,29 +240,13 @@ public class SpellbookScreen extends HandledScreen<SpellbookScreenHandler> {
.setTextureSize(16, 16)
.setSize(16, 16)
.setTexture(trait.getSprite()));
Formatting corruptionColor = trait.getGroup().getCorruption() < -0.01F
? Formatting.GREEN
: trait.getGroup().getCorruption() > 0.25F
? Formatting.RED
: Formatting.WHITE;
getStyle().setTooltip(Tooltip.of(
new TranslatableText("gui.unicopia.trait.label",
new TranslatableText("trait." + trait.getId().getNamespace() + "." + trait.getId().getPath() + ".name")
).formatted(Formatting.YELLOW)
.append(new TranslatableText("gui.unicopia.trait.group", trait.getGroup().name().toLowerCase()).formatted(Formatting.ITALIC, Formatting.GRAY))
.append(new LiteralText("\n\n").formatted(Formatting.WHITE)
.append(new TranslatableText("trait." + trait.getId().getNamespace() + "." + trait.getId().getPath() + ".description").formatted(Formatting.GRAY))
.append("\n")
.append(new TranslatableText("gui.unicopia.trait.corruption", ItemStack.MODIFIER_FORMAT.format(trait.getGroup().getCorruption())).formatted(Formatting.ITALIC, corruptionColor)))
, 200));
getStyle().setTooltip(trait.getTooltip());
onClick(sender -> Pony.of(client.player).getDiscoveries().markRead(trait));
}
@Override
public void renderButton(MatrixStack matrices, int mouseX, int mouseY, float partialTicks) {
public void renderButton(MatrixStack matrices, int mouseX, int mouseY, float tickDelta) {
TraitDiscovery discoveries = Pony.of(client.player).getDiscoveries();
setEnabled(discoveries.isKnown(trait));
@ -266,7 +263,7 @@ public class SpellbookScreen extends HandledScreen<SpellbookScreenHandler> {
drawTexture(matrices, x - 8, y - 8, 225, 219, 35, 32, 512, 256);
}
super.renderButton(matrices, mouseX, mouseY, partialTicks);
super.renderButton(matrices, mouseX, mouseY, tickDelta);
hovered &= active;
}
@ -288,7 +285,7 @@ public class SpellbookScreen extends HandledScreen<SpellbookScreenHandler> {
}
@Override
public void renderButton(MatrixStack matrices, int mouseX, int mouseY, float partialTicks) {
public void renderButton(MatrixStack matrices, int mouseX, int mouseY, float tickDelta) {
RenderSystem.setShader(GameRenderer::getPositionTexShader);
RenderSystem.setShaderColor(1, 1, 1, alpha);
@ -298,7 +295,7 @@ public class SpellbookScreen extends HandledScreen<SpellbookScreenHandler> {
GlStateManager.DstFactor.ONE_MINUS_SRC_ALPHA);
if (getStyle().hasIcon()) {
getStyle().getIcon().render(matrices, x, y, mouseX, mouseY, partialTicks);
getStyle().getIcon().render(matrices, x, y, mouseX, mouseY, tickDelta);
}
RenderSystem.setShaderColor(1, 1, 1, 1);

View file

@ -142,6 +142,9 @@ public class GemstoneItem extends Item {
}
public static ItemStack enchant(ItemStack stack, SpellType<?> type, Affinity affinity) {
if (type.isEmpty()) {
return unenchant(stack);
}
stack.getOrCreateNbt().putString("spell", type.getId().toString());
return type.getTraits().applyTo(stack);
}