From 190194450f91fe4efe02319cd97174d36e8c2ad6 Mon Sep 17 00:00:00 2001 From: ScarletRedMan Date: Mon, 2 Dec 2024 03:49:31 +0700 Subject: [PATCH] feat: base of implementation of dialogue renderer --- .../msb3/api/glyph/font/GlyphFont.java | 15 +- .../LanguageGlyphCollection.java | 3 + .../LanguageGlyphCollectionImpl.java | 18 +- .../msb3/api/glyph/util/KyoriUtil.java | 57 +++ .../api/resource/ResourcePackManager.java | 16 +- .../talk/dialogue/DialogGlyphPositions.java | 82 ++++ .../api/talk/dialogue/DialogueRenderer.java | 440 ++++++++++++++++++ .../dialogue/answer_active_text_field.png | Bin 0 -> 445 bytes .../dialogue/answer_button_active_1.png | Bin 0 -> 281 bytes .../dialogue/answer_button_active_2.png | Bin 0 -> 305 bytes .../dialogue/answer_button_active_3.png | Bin 0 -> 284 bytes .../dialogue/answer_button_active_4.png | Bin 0 -> 301 bytes .../dialogue/answer_not_active_text_field.png | Bin 0 -> 442 bytes .../glyphs/dialogue/avatar_frame.png | Bin 0 -> 648 bytes .../glyphs/dialogue/background_1.png | Bin 0 -> 4893 bytes .../glyphs/dialogue/background_2.png | Bin 0 -> 3909 bytes .../glyphs/dialogue/background_3.png | Bin 0 -> 6208 bytes .../glyphs/dialogue/background_4.png | Bin 0 -> 5957 bytes .../glyphs/dialogue/default_avatar.png | Bin 0 -> 2761 bytes .../glyphs/dialogue/phrase_substrate_1.png | Bin 0 -> 739 bytes .../glyphs/dialogue/phrase_substrate_2.png | Bin 0 -> 796 bytes .../dialogue/scroll_phrase_down_button.png | Bin 0 -> 246 bytes .../dialogue/scroll_phrase_up_button.png | Bin 0 -> 234 bytes 23 files changed, 626 insertions(+), 5 deletions(-) create mode 100644 api/src/main/java/ru/dragonestia/msb3/api/glyph/util/KyoriUtil.java create mode 100644 api/src/main/java/ru/dragonestia/msb3/api/talk/dialogue/DialogGlyphPositions.java create mode 100644 api/src/main/java/ru/dragonestia/msb3/api/talk/dialogue/DialogueRenderer.java create mode 100644 api/src/main/resources/glyphs/dialogue/answer_active_text_field.png create mode 100644 api/src/main/resources/glyphs/dialogue/answer_button_active_1.png create mode 100644 api/src/main/resources/glyphs/dialogue/answer_button_active_2.png create mode 100644 api/src/main/resources/glyphs/dialogue/answer_button_active_3.png create mode 100644 api/src/main/resources/glyphs/dialogue/answer_button_active_4.png create mode 100644 api/src/main/resources/glyphs/dialogue/answer_not_active_text_field.png create mode 100644 api/src/main/resources/glyphs/dialogue/avatar_frame.png create mode 100644 api/src/main/resources/glyphs/dialogue/background_1.png create mode 100644 api/src/main/resources/glyphs/dialogue/background_2.png create mode 100644 api/src/main/resources/glyphs/dialogue/background_3.png create mode 100644 api/src/main/resources/glyphs/dialogue/background_4.png create mode 100644 api/src/main/resources/glyphs/dialogue/default_avatar.png create mode 100644 api/src/main/resources/glyphs/dialogue/phrase_substrate_1.png create mode 100644 api/src/main/resources/glyphs/dialogue/phrase_substrate_2.png create mode 100644 api/src/main/resources/glyphs/dialogue/scroll_phrase_down_button.png create mode 100644 api/src/main/resources/glyphs/dialogue/scroll_phrase_up_button.png diff --git a/api/src/main/java/ru/dragonestia/msb3/api/glyph/font/GlyphFont.java b/api/src/main/java/ru/dragonestia/msb3/api/glyph/font/GlyphFont.java index 2f1640c..6657ebd 100644 --- a/api/src/main/java/ru/dragonestia/msb3/api/glyph/font/GlyphFont.java +++ b/api/src/main/java/ru/dragonestia/msb3/api/glyph/font/GlyphFont.java @@ -25,8 +25,10 @@ public class GlyphFont { public static final LanguageGlyphCollection LANGUAGE_GLYPH = minecraftFontGlyphCollection( MINECRAFT_FONT_KEY, Key.key(MINECRAFT_FONT_KEY.asString().concat(".png")), + MINECRAFT_FONT_IMAGE_WRITABLE, List.of(new TextureProperties(12,-6), new TextureProperties(8,-24), + new TextureProperties(8,8), new TextureProperties(8,-36))); private GlyphFont() {} @@ -35,9 +37,18 @@ public class GlyphFont { return GlyphCompiler.instance().compile(SPACES, LANGUAGE_GLYPH); } - private static @NotNull LanguageGlyphCollection minecraftFontGlyphCollection(@NotNull Key fontKey, @NotNull Key textureKey, @NotNull List<@NotNull TextureProperties> propertiesList) { + public static @NotNull LanguageGlyphCollection minecraftFontGlyphCollection(@NotNull Key fontKey, @NotNull Key textureKey, @NotNull List<@NotNull TextureProperties> propertiesList) { + return minecraftFontGlyphCollection(fontKey, textureKey, MINECRAFT_FONT_IMAGE_WRITABLE, propertiesList); + } + + public static @NotNull LanguageGlyphCollection minecraftFontGlyphCollection( + @NotNull Key fontKey, + @NotNull Key textureKey, + @NotNull Writable writable, + @NotNull List<@NotNull TextureProperties> propertiesList + ) { return LanguageGlyphCollection.of(fontKey, - Texture.texture(textureKey, MINECRAFT_FONT_IMAGE_WRITABLE), + Texture.texture(textureKey, writable), propertiesList, List.of(" АБВГДЕЖЗИК", "ЛМНОПРСТУФХЦЧШЩЪ", diff --git a/api/src/main/java/ru/dragonestia/msb3/api/glyph/glyph/image/multicharacter/LanguageGlyphCollection.java b/api/src/main/java/ru/dragonestia/msb3/api/glyph/glyph/image/multicharacter/LanguageGlyphCollection.java index 3208d92..02ece03 100644 --- a/api/src/main/java/ru/dragonestia/msb3/api/glyph/glyph/image/multicharacter/LanguageGlyphCollection.java +++ b/api/src/main/java/ru/dragonestia/msb3/api/glyph/glyph/image/multicharacter/LanguageGlyphCollection.java @@ -1,6 +1,7 @@ package ru.dragonestia.msb3.api.glyph.glyph.image.multicharacter; import net.kyori.adventure.key.Key; +import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.TextColor; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -32,6 +33,8 @@ public interface LanguageGlyphCollection extends ResourceProducer { @NotNull List<@NotNull AppendableGlyph> translate(int height, int ascent, @NotNull String text, @Nullable TextColor color) throws IllegalArgumentException; + @NotNull List<@NotNull AppendableGlyph> translate(int height, int ascent, @NotNull Component text) throws IllegalArgumentException; + default @NotNull AppendableGlyph translate(int height, int ascent, @NotNull Character character) throws IllegalArgumentException { return translate(height, ascent, character, null); } diff --git a/api/src/main/java/ru/dragonestia/msb3/api/glyph/glyph/image/multicharacter/LanguageGlyphCollectionImpl.java b/api/src/main/java/ru/dragonestia/msb3/api/glyph/glyph/image/multicharacter/LanguageGlyphCollectionImpl.java index 36eba45..4cf78ef 100644 --- a/api/src/main/java/ru/dragonestia/msb3/api/glyph/glyph/image/multicharacter/LanguageGlyphCollectionImpl.java +++ b/api/src/main/java/ru/dragonestia/msb3/api/glyph/glyph/image/multicharacter/LanguageGlyphCollectionImpl.java @@ -1,6 +1,7 @@ package ru.dragonestia.msb3.api.glyph.glyph.image.multicharacter; import net.kyori.adventure.key.Key; +import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.TextColor; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -9,6 +10,7 @@ import ru.dragonestia.msb3.api.glyph.glyph.AppendableGlyph; import ru.dragonestia.msb3.api.glyph.glyph.exception.ResourceAlreadyProducedException; import ru.dragonestia.msb3.api.glyph.glyph.exception.ResourceNotProducedException; import ru.dragonestia.msb3.api.glyph.glyph.image.TextureProperties; +import ru.dragonestia.msb3.api.glyph.util.KyoriUtil; import team.unnamed.creative.font.FontProvider; import team.unnamed.creative.texture.Texture; @@ -85,8 +87,22 @@ public class LanguageGlyphCollectionImpl implements LanguageGlyphCollection { public @NotNull List<@NotNull AppendableGlyph> translate(int height, int ascent, @NotNull String text, @Nullable TextColor color) throws IllegalArgumentException { TextureProperties properties = new TextureProperties(height, ascent); if (!propertiesToMulticharacterMap.containsKey(properties)) { - throw new IllegalArgumentException(); + throw new IllegalArgumentException(properties.toString()); } return propertiesToMulticharacterMap.get(properties).translate(text, color); } + + public @NotNull List<@NotNull AppendableGlyph> translate(int height, int ascent, @NotNull Component component) throws IllegalArgumentException { + var property = new TextureProperties(height, ascent); + var glyphCollection = Objects.requireNonNull(propertiesToMulticharacterMap.get(property), property.toString()); + + var result = new ArrayList(); + List textAndColors = KyoriUtil.toColoredParts(component); + + for (var parts: textAndColors) { + result.addAll(glyphCollection.translate(parts.text(), parts.color())); + } + + return Collections.unmodifiableList(result); + } } diff --git a/api/src/main/java/ru/dragonestia/msb3/api/glyph/util/KyoriUtil.java b/api/src/main/java/ru/dragonestia/msb3/api/glyph/util/KyoriUtil.java new file mode 100644 index 0000000..0bea98b --- /dev/null +++ b/api/src/main/java/ru/dragonestia/msb3/api/glyph/util/KyoriUtil.java @@ -0,0 +1,57 @@ +package ru.dragonestia.msb3.api.glyph.util; + +import lombok.experimental.UtilityClass; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.flattener.ComponentFlattener; +import net.kyori.adventure.text.flattener.FlattenerListener; +import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.format.Style; +import net.kyori.adventure.text.format.TextColor; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.Deque; +import java.util.LinkedList; +import java.util.List; + +@UtilityClass +public class KyoriUtil { + + public @NotNull List toColoredParts(@NotNull Component component) { + var listener = new ColoredPartsFlattenerListener(); + ComponentFlattener.basic().flatten(component, listener); + return listener.result(); + } + + public static class ColoredPartsFlattenerListener implements FlattenerListener { + + private final Deque colors = new LinkedList<>(); + private final List result = new ArrayList<>(); + + public void pushStyle(@NotNull Style style) { + var color = style.color(); + if (color != null) colors.add(color); + } + + public void component(@NotNull String text) { + result.add(new ColoredComponentTextPart(text, current())); + } + + public void popStyle(@NotNull Style style) { + var color = style.color(); + if (color != null) colors.removeLast(); + + } + + private TextColor current() { + var color = colors.peekLast(); + return color != null ? color : NamedTextColor.WHITE; + } + + public @NotNull List result() { + return result; + } + + public record ColoredComponentTextPart(String text, TextColor color) {} + } +} diff --git a/api/src/main/java/ru/dragonestia/msb3/api/resource/ResourcePackManager.java b/api/src/main/java/ru/dragonestia/msb3/api/resource/ResourcePackManager.java index 85fcfdf..adac065 100644 --- a/api/src/main/java/ru/dragonestia/msb3/api/resource/ResourcePackManager.java +++ b/api/src/main/java/ru/dragonestia/msb3/api/resource/ResourcePackManager.java @@ -7,17 +7,18 @@ import ru.dragonestia.msb3.api.glyph.compile.GlyphCompiler; import ru.dragonestia.msb3.api.glyph.font.GlyphFont; import ru.dragonestia.msb3.api.glyph.pack.GlyphResourcePack; import ru.dragonestia.msb3.api.item.BlankSlotItem; +import ru.dragonestia.msb3.api.talk.dialogue.DialogueRenderer; import ru.dragonestia.msb3.api.title.BlackScreen; import team.unnamed.creative.ResourcePack; import team.unnamed.creative.atlas.Atlas; import team.unnamed.creative.atlas.AtlasSource; import team.unnamed.creative.base.Writable; +import team.unnamed.creative.lang.Language; import team.unnamed.creative.model.Model; import team.unnamed.creative.serialize.minecraft.MinecraftResourcePackWriter; import java.io.File; -import java.util.HashSet; -import java.util.Set; +import java.util.*; @Getter @Log4j2 @@ -35,7 +36,9 @@ public class ResourcePackManager { glyphResourcePack = GlyphResourcePack.create(); monologueResources = new MonologueResources(glyphResourcePack); + DialogueRenderer.compile(glyphResourcePack); initDefaultGlyphs(); + initLocales(); } private void initDefaultGlyphs() { @@ -46,6 +49,15 @@ public class ResourcePackManager { monologueResources.compile(glyphResourcePack); } + private void initLocales() { + var locales = new HashMap(); + locales.put("container.inventory", " "); + + for (var lang: List.of("en_us", "ru_ru")) { + resourcePack.language(Language.language(Key.key("minecraft", lang), locales)); + } + } + public void compile() { glyphResourcePack.writeAll(resourcePack); generateAtlases(); diff --git a/api/src/main/java/ru/dragonestia/msb3/api/talk/dialogue/DialogGlyphPositions.java b/api/src/main/java/ru/dragonestia/msb3/api/talk/dialogue/DialogGlyphPositions.java new file mode 100644 index 0000000..a387abc --- /dev/null +++ b/api/src/main/java/ru/dragonestia/msb3/api/talk/dialogue/DialogGlyphPositions.java @@ -0,0 +1,82 @@ +package ru.dragonestia.msb3.api.talk.dialogue; + +import net.kyori.adventure.text.format.Style; +import net.kyori.adventure.text.format.TextColor; +import net.kyori.adventure.text.format.TextDecoration; +import ru.dragonestia.msb3.api.glyph.glyph.image.TextureProperties; + +public record DialogGlyphPositions(GuiBackground guiBackground, + PhraseText phraseText, + PhraseSubstrate phraseSubstrate, + Avatar avatar, + AnswerText answerText, + AnswerField answerField, + AnswerButton answerButton, + ScrollPhraseButton scrollPhraseButton) { + + public static final DialogGlyphPositions DEFAULT = new DialogGlyphPositions( + new GuiBackground(35, -96, 262), + new PhraseText(11, -11, 340, 8, 8, "#000000"), + new PhraseSubstrate(-24, 19, 356, 116), + new Avatar(-144, 11, 100, 116), + new AnswerText(175, 4, 8, -145, 149, -122, -170, "#000000"), + new AnswerField(-117, -165, -153, 141, 44), + new AnswerButton(-124, -182, 41, 97, 20, "#a18262", "#ffd8a8"), + new ScrollPhraseButton(0, 126, -100, 10, "#a18262") + ); + + public record GuiBackground(int topPartsY, int bottomPartsY, int height) {} + + public record PhraseText(int maxLines, int lineX, int lineWidth, int fontHeight, int firstLineAscent, String textColorHex) { + + public TextColor textColor() { + return TextColor.fromHexString(textColorHex); + } + + public TextureProperties firstLineProperties() { + return new TextureProperties(fontHeight, firstLineAscent); + } + } + + public record PhraseSubstrate(int x, int y, int width, int height) {} + + public record Avatar(int x, int y, int height, int frameHeight) { + + public int frameBorderSize() { + return (frameHeight - height) / 2; + } + } + + public record AnswerText(int lineWidth, int maxLines, int fontHeight, int leftLineX, + int rightLineX, int topFirstLineAscent, int bottomFirstLineAscent, + String textColorHex) { + + public TextColor textColor() { + return TextColor.fromHexString(textColorHex); + } + } + + public record AnswerField(int topFieldY, int bottomFieldY, int leftFieldX, int rightFieldX, int height) {} + + public record AnswerButton(int topButtonY, int bottomButtonY, int leftButtonX, + int rightButtonX, int height, + String loreHeadingTextColorHex, + String loreFallbackTextColorHex) { + + public Style loreHeadingTextStyle() { + return Style.style(TextColor.fromHexString(loreHeadingTextColorHex), TextDecoration.ITALIC.withState(false)); + } + + public Style loreFallbackTextStyle() { + return Style.style(TextColor.fromHexString(loreFallbackTextColorHex), TextDecoration.ITALIC.withState(false)); + } + } + + public record ScrollPhraseButton(int scrollUpButtonX, int scrollDownButtonX, int buttonY, int height, String textColorHex) { + + public Style textStyle() { + return Style.style(TextColor.fromHexString(textColorHex), + TextDecoration.ITALIC.withState(false)); + } + } +} diff --git a/api/src/main/java/ru/dragonestia/msb3/api/talk/dialogue/DialogueRenderer.java b/api/src/main/java/ru/dragonestia/msb3/api/talk/dialogue/DialogueRenderer.java new file mode 100644 index 0000000..819340f --- /dev/null +++ b/api/src/main/java/ru/dragonestia/msb3/api/talk/dialogue/DialogueRenderer.java @@ -0,0 +1,440 @@ +package ru.dragonestia.msb3.api.talk.dialogue; + +import net.kyori.adventure.key.Key; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.TextComponent; +import net.kyori.adventure.text.format.Style; +import net.kyori.adventure.text.format.TextColor; +import net.kyori.adventure.text.minimessage.MiniMessage; +import net.minestom.server.entity.Player; +import net.minestom.server.inventory.Inventory; +import net.minestom.server.inventory.InventoryType; +import org.jetbrains.annotations.NotNull; +import ru.dragonestia.msb3.api.ServerBootstrap; +import ru.dragonestia.msb3.api.glyph.font.GlyphFont; +import ru.dragonestia.msb3.api.glyph.glyph.AppendableGlyph; +import ru.dragonestia.msb3.api.glyph.glyph.GlyphComponentBuilder; +import ru.dragonestia.msb3.api.glyph.glyph.image.ImageGlyph; +import ru.dragonestia.msb3.api.glyph.glyph.image.TextureProperties; +import ru.dragonestia.msb3.api.glyph.glyph.image.multicharacter.LanguageGlyphCollection; +import ru.dragonestia.msb3.api.glyph.pack.GlyphResourcePack; +import ru.dragonestia.msb3.api.glyph.pack.StringIdentifier; +import ru.dragonestia.msb3.api.util.ResourceFromJar; +import ru.dragonestia.msb3.api.util.StringUtil; +import team.unnamed.creative.base.Writable; +import team.unnamed.creative.texture.Texture; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.IntStream; +import java.util.stream.Stream; + +public class DialogueRenderer { + + public static final int CHEST_GUI_WIDTH = 176; + + private static final Key DIALOGUE_FONT_KEY = Key.key("msb3", "dialog"); + + private static final Writable ANSWER_ACTIVE_TEXT_FIELD = ResourceFromJar.of("glyphs/dialogue/answer_active_text_field.png"); + private static final Writable ANSWER_BUTTON_ACTIVE_1 = ResourceFromJar.of("glyphs/dialogue/answer_button_active_1.png"); + private static final Writable ANSWER_BUTTON_ACTIVE_2 = ResourceFromJar.of("glyphs/dialogue/answer_button_active_2.png"); + private static final Writable ANSWER_BUTTON_ACTIVE_3 = ResourceFromJar.of("glyphs/dialogue/answer_button_active_3.png"); + private static final Writable ANSWER_BUTTON_ACTIVE_4 = ResourceFromJar.of("glyphs/dialogue/answer_button_active_4.png"); + private static final Writable ANSWER_NOT_ACTIVE_TEXT_FIELD = ResourceFromJar.of("glyphs/dialogue/answer_not_active_text_field.png"); + private static final Writable AVATAR_FRAME = ResourceFromJar.of("glyphs/dialogue/avatar_frame.png"); + private static final Writable BACKGROUND_1 = ResourceFromJar.of("glyphs/dialogue/background_1.png"); + private static final Writable BACKGROUND_2 = ResourceFromJar.of("glyphs/dialogue/background_2.png"); + private static final Writable BACKGROUND_3 = ResourceFromJar.of("glyphs/dialogue/background_3.png"); + private static final Writable BACKGROUND_4 = ResourceFromJar.of("glyphs/dialogue/background_4.png"); + private static final Writable DEFAULT_AVATAR = ResourceFromJar.of("glyphs/dialogue/default_avatar.png"); + private static final Writable PHRASE_SUBSTRATE_1 = ResourceFromJar.of("glyphs/dialogue/phrase_substrate_1.png"); + private static final Writable PHRASE_SUBSTRATE_2 = ResourceFromJar.of("glyphs/dialogue/phrase_substrate_2.png"); + private static final Writable SCROLL_PHRASE_DOWN_BUTTON = ResourceFromJar.of("glyphs/dialogue/scroll_phrase_down_button.png"); + private static final Writable SCROLL_PHRASE_UP_BUTTON = ResourceFromJar.of("glyphs/dialogue/scroll_phrase_up_button.png"); + + private static final StringIdentifier<@NotNull ImageGlyph> ID_ANSWER_ACTIVE_TEXT_FIELD_1 = StringIdentifier.image("dialog_answer_active_text_field_1"); + private static final StringIdentifier<@NotNull ImageGlyph> ID_ANSWER_ACTIVE_TEXT_FIELD_2 = StringIdentifier.image("dialog_answer_active_text_field_2"); + private static final StringIdentifier<@NotNull ImageGlyph> ID_ANSWER_ACTIVE_TEXT_FIELD_3 = StringIdentifier.image("dialog_answer_active_text_field_3"); + private static final StringIdentifier<@NotNull ImageGlyph> ID_ANSWER_ACTIVE_TEXT_FIELD_4 = StringIdentifier.image("dialog_answer_active_text_field_4"); + private static final StringIdentifier<@NotNull ImageGlyph> ID_ANSWER_BUTTON_ACTIVE_1 = StringIdentifier.image("dialog_answer_button_active_1"); + private static final StringIdentifier<@NotNull ImageGlyph> ID_ANSWER_BUTTON_ACTIVE_2 = StringIdentifier.image("dialog_answer_button_active_2"); + private static final StringIdentifier<@NotNull ImageGlyph> ID_ANSWER_BUTTON_ACTIVE_3 = StringIdentifier.image("dialog_answer_button_active_3"); + private static final StringIdentifier<@NotNull ImageGlyph> ID_ANSWER_BUTTON_ACTIVE_4 = StringIdentifier.image("dialog_answer_button_active_4"); + private static final StringIdentifier<@NotNull ImageGlyph> ID_ANSWER_NOT_ACTIVE_TEXT_FIELD_1 = StringIdentifier.image("dialog_answer_not_active_text_field_1"); + private static final StringIdentifier<@NotNull ImageGlyph> ID_ANSWER_NOT_ACTIVE_TEXT_FIELD_2 = StringIdentifier.image("dialog_answer_not_active_text_field_2"); + private static final StringIdentifier<@NotNull ImageGlyph> ID_ANSWER_NOT_ACTIVE_TEXT_FIELD_3 = StringIdentifier.image("dialog_answer_not_active_text_field_3"); + private static final StringIdentifier<@NotNull ImageGlyph> ID_ANSWER_NOT_ACTIVE_TEXT_FIELD_4 = StringIdentifier.image("dialog_answer_not_active_text_field_4"); + private static final StringIdentifier<@NotNull ImageGlyph> ID_AVATAR_FRAME = StringIdentifier.image("dialog_avatar_frame"); + private static final StringIdentifier<@NotNull ImageGlyph> ID_BACKGROUND_1 = StringIdentifier.image("dialog_background_1"); + private static final StringIdentifier<@NotNull ImageGlyph> ID_BACKGROUND_2 = StringIdentifier.image("dialog_background_2"); + private static final StringIdentifier<@NotNull ImageGlyph> ID_BACKGROUND_3 = StringIdentifier.image("dialog_background_3"); + private static final StringIdentifier<@NotNull ImageGlyph> ID_BACKGROUND_4 = StringIdentifier.image("dialog_background_4"); + private static final StringIdentifier<@NotNull ImageGlyph> ID_DEFAULT_AVATAR = StringIdentifier.image("dialog_default_avatar"); + private static final StringIdentifier<@NotNull ImageGlyph> ID_PHRASE_SUBSTRATE_1 = StringIdentifier.image("dialog_phrase_substrate_1"); + private static final StringIdentifier<@NotNull ImageGlyph> ID_PHRASE_SUBSTRATE_2 = StringIdentifier.image("dialog_phrase_substrate_2"); + private static final StringIdentifier<@NotNull ImageGlyph> ID_SCROLL_PHRASE_DOWN_BUTTON = StringIdentifier.image("dialog_scroll_phrase_down_button"); + private static final StringIdentifier<@NotNull ImageGlyph> ID_SCROLL_PHRASE_UP_BUTTON = StringIdentifier.image("dialog_scroll_phrase_up_button"); + + private static final ImageGlyph GLYPH_ANSWER_ACTIVE_TEXT_FIELD_1 = createGlyph("dialog/answer_active_text_field.png", + ANSWER_ACTIVE_TEXT_FIELD, + DialogGlyphPositions.DEFAULT.answerField().height(), + DialogGlyphPositions.DEFAULT.answerField().topFieldY()); + private static final ImageGlyph GLYPH_ANSWER_ACTIVE_TEXT_FIELD_2 = createGlyph("dialog/answer_active_text_field.png", + ANSWER_ACTIVE_TEXT_FIELD, + DialogGlyphPositions.DEFAULT.answerField().height(), + DialogGlyphPositions.DEFAULT.answerField().topFieldY()); + private static final ImageGlyph GLYPH_ANSWER_ACTIVE_TEXT_FIELD_3 = createGlyph("dialog/answer_active_text_field.png", + ANSWER_ACTIVE_TEXT_FIELD, + DialogGlyphPositions.DEFAULT.answerField().height(), + DialogGlyphPositions.DEFAULT.answerField().bottomFieldY()); + private static final ImageGlyph GLYPH_ANSWER_ACTIVE_TEXT_FIELD_4 = createGlyph("dialog/answer_active_text_field.png", + ANSWER_ACTIVE_TEXT_FIELD, + DialogGlyphPositions.DEFAULT.answerField().height(), + DialogGlyphPositions.DEFAULT.answerField().bottomFieldY()); + private static final ImageGlyph GLYPH_ANSWER_BUTTON_ACTIVE_1 = createGlyph("dialog/answer_button_active_1.png", + ANSWER_BUTTON_ACTIVE_1, + DialogGlyphPositions.DEFAULT.answerButton().height(), + DialogGlyphPositions.DEFAULT.answerButton().topButtonY()); + private static final ImageGlyph GLYPH_ANSWER_BUTTON_ACTIVE_2 = createGlyph("dialog/answer_button_active_2.png", + ANSWER_BUTTON_ACTIVE_2, + DialogGlyphPositions.DEFAULT.answerButton().height(), + DialogGlyphPositions.DEFAULT.answerButton().topButtonY()); + private static final ImageGlyph GLYPH_ANSWER_BUTTON_ACTIVE_3 = createGlyph("dialog/answer_button_active_3.png", + ANSWER_BUTTON_ACTIVE_3, + DialogGlyphPositions.DEFAULT.answerButton().height(), + DialogGlyphPositions.DEFAULT.answerButton().bottomButtonY()); + private static final ImageGlyph GLYPH_ANSWER_BUTTON_ACTIVE_4 = createGlyph("dialog/answer_button_active_4.png", + ANSWER_BUTTON_ACTIVE_4, + DialogGlyphPositions.DEFAULT.answerButton().height(), + DialogGlyphPositions.DEFAULT.answerButton().bottomButtonY()); + private static final ImageGlyph GLYPH_ANSWER_NOT_ACTIVE_TEXT_FIELD_1 = createGlyph("dialog/answer_not_active_text_field.png", + ANSWER_NOT_ACTIVE_TEXT_FIELD, + DialogGlyphPositions.DEFAULT.answerField().height(), + DialogGlyphPositions.DEFAULT.answerField().topFieldY()); + private static final ImageGlyph GLYPH_ANSWER_NOT_ACTIVE_TEXT_FIELD_2 = createGlyph("dialog/answer_not_active_text_field.png", + ANSWER_NOT_ACTIVE_TEXT_FIELD, + DialogGlyphPositions.DEFAULT.answerField().height(), + DialogGlyphPositions.DEFAULT.answerField().topFieldY()); + private static final ImageGlyph GLYPH_ANSWER_NOT_ACTIVE_TEXT_FIELD_3 = createGlyph("dialog/answer_not_active_text_field.png", + ANSWER_NOT_ACTIVE_TEXT_FIELD, + DialogGlyphPositions.DEFAULT.answerField().height(), + DialogGlyphPositions.DEFAULT.answerField().bottomFieldY()); + private static final ImageGlyph GLYPH_ANSWER_NOT_ACTIVE_TEXT_FIELD_4 = createGlyph("dialog/answer_not_active_text_field.png", + ANSWER_NOT_ACTIVE_TEXT_FIELD, + DialogGlyphPositions.DEFAULT.answerField().height(), + DialogGlyphPositions.DEFAULT.answerField().bottomFieldY()); + private static final ImageGlyph GLYPH_AVATAR_FRAME = createGlyph("dialog/avatar_frame.png", + AVATAR_FRAME, + DialogGlyphPositions.DEFAULT.avatar().frameHeight(), + DialogGlyphPositions.DEFAULT.avatar().y() + DialogGlyphPositions.DEFAULT.avatar().frameBorderSize()); + private static final ImageGlyph GLYPH_BACKGROUND_1 = createGlyph("dialog/background_1.png", + BACKGROUND_1, + DialogGlyphPositions.DEFAULT.guiBackground().height() / 2, + DialogGlyphPositions.DEFAULT.guiBackground().topPartsY()); + private static final ImageGlyph GLYPH_BACKGROUND_2 = createGlyph("dialog/background_2.png", + BACKGROUND_2, + DialogGlyphPositions.DEFAULT.guiBackground().height() / 2, + DialogGlyphPositions.DEFAULT.guiBackground().topPartsY()); + private static final ImageGlyph GLYPH_BACKGROUND_3 = createGlyph("dialog/background_3.png", + BACKGROUND_3, + DialogGlyphPositions.DEFAULT.guiBackground().height() / 2, + DialogGlyphPositions.DEFAULT.guiBackground().bottomPartsY()); + private static final ImageGlyph GLYPH_BACKGROUND_4 = createGlyph("dialog/background_4.png", + BACKGROUND_4, + DialogGlyphPositions.DEFAULT.guiBackground().height() / 2, + DialogGlyphPositions.DEFAULT.guiBackground().bottomPartsY()); + private static final ImageGlyph GLYPH_DEFAULT_AVATAR = createGlyph("dialog/default_avatar.png", + DEFAULT_AVATAR, + DialogGlyphPositions.DEFAULT.avatar().height(), + DialogGlyphPositions.DEFAULT.avatar().y()); + private static final ImageGlyph GLYPH_PHRASE_SUBSTRATE_1 = createGlyph("dialog/phrase_substrate_1.png", + PHRASE_SUBSTRATE_1, + DialogGlyphPositions.DEFAULT.phraseSubstrate().height(), + DialogGlyphPositions.DEFAULT.phraseSubstrate().y()); + private static final ImageGlyph GLYPH_PHRASE_SUBSTRATE_2 = createGlyph("dialog/phrase_substrate_2.png", + PHRASE_SUBSTRATE_2, + DialogGlyphPositions.DEFAULT.phraseSubstrate().height(), + DialogGlyphPositions.DEFAULT.phraseSubstrate().y()); + private static final ImageGlyph GLYPH_SCROLL_PHRASE_DOWN_BUTTON = createGlyph("dialog/scroll_phrase_down_button.png", + SCROLL_PHRASE_DOWN_BUTTON, + DialogGlyphPositions.DEFAULT.scrollPhraseButton().height(), + DialogGlyphPositions.DEFAULT.scrollPhraseButton().buttonY()); + private static final ImageGlyph GLYPH_SCROLL_PHRASE_UP_BUTTON = createGlyph("dialog/scroll_phrase_up_button.png", + SCROLL_PHRASE_UP_BUTTON, + DialogGlyphPositions.DEFAULT.scrollPhraseButton().height(), + DialogGlyphPositions.DEFAULT.scrollPhraseButton().buttonY()); + + private static final LanguageGlyphCollection GLYPH_FONT; + private static final StringIdentifier<@NotNull LanguageGlyphCollection> ID_FONT = StringIdentifier.of("dialog_font", LanguageGlyphCollection.class); + + static { + var positions = DialogGlyphPositions.DEFAULT; + var propertiesList = Stream.concat(Stream.concat(IntStream.range(0, positions.phraseText().maxLines()).map((idx) -> { + return idx * (positions.phraseText().fontHeight() + 1) + positions.phraseText().firstLineAscent() * -1; + }).boxed().map((ascent) -> { + return new TextureProperties(positions.phraseText().fontHeight(), ascent * -1); + }), IntStream.range(0, positions.answerText().maxLines()).map((idx) -> { + return idx * (positions.answerText().fontHeight() + 1) + positions.answerText().topFirstLineAscent() * -1; + }).boxed().map((ascent) -> { + return new TextureProperties(positions.answerText().fontHeight(), ascent * -1); + })), IntStream.range(0, positions.answerText().maxLines()).map((idx) -> { + return idx * (positions.answerText().fontHeight() + 1) + positions.answerText().bottomFirstLineAscent() * -1; + }).boxed().map((ascent) -> { + return new TextureProperties(positions.answerText().fontHeight(), ascent * -1); + })).toList(); + + GLYPH_FONT = GlyphFont.minecraftFontGlyphCollection(DIALOGUE_FONT_KEY, Key.key("dialog/font.png"), propertiesList); + } + + private final Player player; + private final String text = """ + Абсолютно точно. + Я знаю точнo - невозможное возможно + Сойти с ума, влюбиться так неосторoжно + Найти тебя, не отпускать ни днём, ни ночью + Всё невозможное - возможно, знаю точно! + А где тебя искать, прошу ты мне ответь + В какие города мне за тобой лететь + Я готов на край Земли, я всё должен объяснить + Пойми, что без тебя я не умею жить + Я знаю точно - невозможное возможно + Сойти с ума, влюбиться так неосторожно + Найти тебя, не отпускать ни днём, ни ночью + Всё невозможное - возможно, знаю точно! + На-на-на-на (на-на-на-на), а-а, а-а + На-на-на-на (на-на-на-на), а-а, а-а + Всё готов делить, с тобой я пополам + Ты только мне поверь, я сделал выбор сам + Дай же мне последний шанс, я всё должен объяснить + Пойми, что без тебя я не умею жить + Я знаю точно - невозможное возможно + Сойти с ума, влюбиться так неосторожно + Найти тебя, не отпускать ни днём, ни ночью + Всё невозможное - возможно, знаю точно! + На-на-на-на (на-на-на-на), а-а, а-а + На-на-на-на (на-на-на-на), а-а, а-а + Я знаю точно - невозможное возможно + Сойти с ума, влюбиться так неосторожно + Найти тебя, не отпускать ни днём, ни ночью + Всё невозможное - возможно, знаю точно! + На-на-на-на (на-на-на-на), а-а, а-а + На-на-на-на (на-на-на-на), а-а, а-а"""; + + private DialogueRenderer(Player player) { + this.player = player; + + + } + + public static DialogueRenderer create(Player player) { + return new DialogueRenderer(player); + } + + public void show() { + var inv = new Inventory(InventoryType.CHEST_6_ROW, render(ServerBootstrap.getInstance().getResourcePackManager().getGlyphResourcePack())); + player.openInventory(inv); + } + + private Component render(GlyphResourcePack resourcePack) { + var positions = DialogGlyphPositions.DEFAULT; + + var avatar = resourcePack.get(ID_DEFAULT_AVATAR); + var avatarFrame = resourcePack.get(ID_AVATAR_FRAME); + var guiBackground1 = resourcePack.get(ID_BACKGROUND_1); + var guiBackground2 = resourcePack.get(ID_BACKGROUND_2); + var guiBackground3 = resourcePack.get(ID_BACKGROUND_3); + var guiBackground4 = resourcePack.get(ID_BACKGROUND_4); + var phraseSubstrate1 = resourcePack.get(ID_PHRASE_SUBSTRATE_1); + var phraseSubstrate2 = resourcePack.get(ID_PHRASE_SUBSTRATE_2); + + var builder = GlyphComponentBuilder.gui(resourcePack.spaces()); + + // GUI background + builder.append(CHEST_GUI_WIDTH / 2 + 2 - guiBackground1.width(), guiBackground1); + builder.append(CHEST_GUI_WIDTH / 2 + 1, guiBackground2); + builder.append(CHEST_GUI_WIDTH / 2 + 2 - guiBackground3.width(), guiBackground3); + builder.append(CHEST_GUI_WIDTH / 2 + 1, guiBackground4); + + // Phrase substrate + builder.append(positions.phraseSubstrate().x(), phraseSubstrate1); + builder.append(positions.phraseSubstrate().x() + positions.phraseSubstrate().width() / 2, phraseSubstrate2); + + // Frame and avatar + builder.append(positions.avatar().x() - 8, avatarFrame); + builder.append(positions.avatar().x(), avatar); + + // Phrase text + renderText(builder, 0); + + // Scroll phrase buttons (optional) + builder.append( + positions.scrollPhraseButton().scrollUpButtonX(), + resourcePack.get(ID_SCROLL_PHRASE_UP_BUTTON)); + + builder.append( + positions.scrollPhraseButton().scrollDownButtonX(), + resourcePack.get(ID_SCROLL_PHRASE_DOWN_BUTTON)); + + // Answers + + var answers = List.of("Hello world!", "I am a teapot", "I love pizza", "msb3 is top!"); + for (int i = 0; i < 4; i++) { + if (true) { //TODO: checking answer exists + builder.append(i % 2 == 0 + ? positions.answerField().leftFieldX() + : positions.answerField().rightFieldX(), + switch (i) { + case 0 -> resourcePack.get(ID_ANSWER_ACTIVE_TEXT_FIELD_1); + case 1 -> resourcePack.get(ID_ANSWER_ACTIVE_TEXT_FIELD_2); + case 2 -> resourcePack.get(ID_ANSWER_ACTIVE_TEXT_FIELD_3); + case 3 -> resourcePack.get(ID_ANSWER_ACTIVE_TEXT_FIELD_4); + default -> throw new IllegalStateException("Unexpected value: " + i); + }); + + builder.append(i % 2 == 0 + ? positions.answerButton().leftButtonX() + : positions.answerButton().rightButtonX(), + switch (i) { + case 0 -> resourcePack.get(ID_ANSWER_BUTTON_ACTIVE_1); + case 1 -> resourcePack.get(ID_ANSWER_BUTTON_ACTIVE_2); + case 2 -> resourcePack.get(ID_ANSWER_BUTTON_ACTIVE_3); + case 3 -> resourcePack.get(ID_ANSWER_BUTTON_ACTIVE_4); + default -> throw new IllegalStateException("Unexpected value: " + i); + }); + + var lines = breakIntoLines( + answers.get(i), + i < 2 + ? new TextureProperties(positions.answerText().fontHeight(), + positions.answerText().topFirstLineAscent()) + : new TextureProperties( + positions.answerText().fontHeight(), + positions.answerText().bottomFirstLineAscent()), + positions.answerText().fontHeight(), + positions.answerText().lineWidth()); + + for (int lineIdx = 0; + lineIdx < Math.min(lines.size(), positions.answerText().maxLines()); + lineIdx++) { + + boolean endWithDots = lineIdx + 1 == positions.answerText().maxLines() + && lineIdx + 1 != lines.size(); + + var line = lines.get(lineIdx); + builder.append(i % 2 == 0 + ? positions.answerText().leftLineX() + : positions.answerText().rightLineX(), line.toGlyphList(0, endWithDots, positions.answerText().textColor())); + } + } else { + builder.append(i % 2 == 0 + ? positions.answerField().leftFieldX() + : positions.answerField().rightFieldX(), + switch (i) { + case 0 -> resourcePack.get(ID_ANSWER_NOT_ACTIVE_TEXT_FIELD_1); + case 1 -> resourcePack.get(ID_ANSWER_NOT_ACTIVE_TEXT_FIELD_2); + case 2 -> resourcePack.get(ID_ANSWER_NOT_ACTIVE_TEXT_FIELD_3); + case 3 -> resourcePack.get(ID_ANSWER_NOT_ACTIVE_TEXT_FIELD_4); + default -> throw new IllegalStateException("Unexpected value: " + i); + }); + } + } + + return builder.build(); + } + + private List breakIntoLines(String input, TextureProperties firstLineProperties, int fontHeight, int maxWidth) throws IllegalStateException { + var resolvedText = MiniMessage.miniMessage().deserialize(input); + var lines = StringUtil.splitIntoParts(resolvedText, maxWidth, line -> { + int width = 0; + for (var glyph: GLYPH_FONT.translate(firstLineProperties.height(), firstLineProperties.ascent(), line)) { + width += glyph.width(); + } + return width; + }); + + if (lines == null) { + throw new IllegalStateException("Cannot fit text into dialog"); + } + + var textLines = new ArrayList(); + for (int lineIdx = 0; lineIdx < lines.length; lineIdx++) { + var text = lines[lineIdx]; + + textLines.add(new TextLine(text, new TextureProperties(firstLineProperties.height(), firstLineProperties.ascent() - lineIdx * (fontHeight + 1)))); + } + return textLines; + } + + private void renderText(GlyphComponentBuilder builder, int shift) { + var positions = DialogGlyphPositions.DEFAULT; + + var textLines = breakIntoLines( + text, + positions.phraseText().firstLineProperties(), + positions.phraseText().fontHeight(), + positions.phraseText().lineWidth()); + + for (int lineIdx = shift; (lineIdx - shift) < Math.min(textLines.size() - shift, positions.phraseText().maxLines()); lineIdx++) { + var line = textLines.get(lineIdx); + builder.append(positions.phraseText().lineX(), line.toGlyphList( + -lineIdx + shift, + textLines.size() - lineIdx > 0 && lineIdx - shift == positions.phraseText().maxLines() - 1, + positions.phraseText().textColor() + )); + } + } + + public static void compile(GlyphResourcePack resourcePack) { + resourcePack.with(ID_ANSWER_ACTIVE_TEXT_FIELD_1, GLYPH_ANSWER_ACTIVE_TEXT_FIELD_1) + .with(ID_ANSWER_ACTIVE_TEXT_FIELD_2, GLYPH_ANSWER_ACTIVE_TEXT_FIELD_2) + .with(ID_ANSWER_ACTIVE_TEXT_FIELD_3, GLYPH_ANSWER_ACTIVE_TEXT_FIELD_3) + .with(ID_ANSWER_ACTIVE_TEXT_FIELD_4, GLYPH_ANSWER_ACTIVE_TEXT_FIELD_4) + .with(ID_ANSWER_BUTTON_ACTIVE_1, GLYPH_ANSWER_BUTTON_ACTIVE_1) + .with(ID_ANSWER_BUTTON_ACTIVE_2, GLYPH_ANSWER_BUTTON_ACTIVE_2) + .with(ID_ANSWER_BUTTON_ACTIVE_3, GLYPH_ANSWER_BUTTON_ACTIVE_3) + .with(ID_ANSWER_BUTTON_ACTIVE_4, GLYPH_ANSWER_BUTTON_ACTIVE_4) + .with(ID_ANSWER_NOT_ACTIVE_TEXT_FIELD_1, GLYPH_ANSWER_NOT_ACTIVE_TEXT_FIELD_1) + .with(ID_ANSWER_NOT_ACTIVE_TEXT_FIELD_2, GLYPH_ANSWER_NOT_ACTIVE_TEXT_FIELD_2) + .with(ID_ANSWER_NOT_ACTIVE_TEXT_FIELD_3, GLYPH_ANSWER_NOT_ACTIVE_TEXT_FIELD_3) + .with(ID_ANSWER_NOT_ACTIVE_TEXT_FIELD_4, GLYPH_ANSWER_NOT_ACTIVE_TEXT_FIELD_4) + .with(ID_AVATAR_FRAME, GLYPH_AVATAR_FRAME) + .with(ID_BACKGROUND_1, GLYPH_BACKGROUND_1) + .with(ID_BACKGROUND_2, GLYPH_BACKGROUND_2) + .with(ID_BACKGROUND_3, GLYPH_BACKGROUND_3) + .with(ID_BACKGROUND_4, GLYPH_BACKGROUND_4) + .with(ID_DEFAULT_AVATAR, GLYPH_DEFAULT_AVATAR) + .with(ID_PHRASE_SUBSTRATE_1, GLYPH_PHRASE_SUBSTRATE_1) + .with(ID_PHRASE_SUBSTRATE_2, GLYPH_PHRASE_SUBSTRATE_2) + .with(ID_SCROLL_PHRASE_DOWN_BUTTON, GLYPH_SCROLL_PHRASE_DOWN_BUTTON) + .with(ID_SCROLL_PHRASE_UP_BUTTON, GLYPH_SCROLL_PHRASE_UP_BUTTON) + .with(ID_FONT, GLYPH_FONT); + } + + private static ImageGlyph createGlyph(String texturePath, Writable writable, int height, int ascent) { + return ImageGlyph.of(DIALOGUE_FONT_KEY, + Texture.texture().key(Key.key("msb3", texturePath)).data(writable).build(), + new TextureProperties(height, ascent)); + } + + private record TextLine(Component text, TextureProperties textureProperties) { + + public List<@NotNull AppendableGlyph> toGlyphList(int lineShift, boolean cutEnding, TextColor color) { + var positions = DialogGlyphPositions.DEFAULT; + + var properties = new TextureProperties( + positions.phraseText().firstLineProperties().height(), + positions.phraseText().firstLineProperties().ascent() + lineShift * (textureProperties().height() + 1) + ); + + Component text = this.text; + if (cutEnding) text = StringUtil.cutEnding(text, " <...>"); + text = text.applyFallbackStyle(Style.style(color)); + + return toList((TextComponent) text, properties); + } + + private List toList(TextComponent text, TextureProperties properties) { + return GLYPH_FONT.translate(properties.height(), properties.ascent(), text); + } + } +} diff --git a/api/src/main/resources/glyphs/dialogue/answer_active_text_field.png b/api/src/main/resources/glyphs/dialogue/answer_active_text_field.png new file mode 100644 index 0000000000000000000000000000000000000000..2f3bd34eb176d3daaf7bc0a34c094720577428f9 GIT binary patch literal 445 zcmeAS@N?(olHy`uVBq!ia0vp^`+!)7gAGXDzQuGENHG=%xjQkeJ16rJ$S(JEaSW-L z^Y)Id7jvM<(T~OtTfa2jSnBz3-JSOC0!Fi@ZUOFK4M7=J&-C24$C*?bBkmqN(;oZm zp!NUwVw)R2h4;^@_ypDNz5kwN#)|pzDxQHu6!~H-2ombo0kf zf2Fy0Y3Yv-pL$?x_dp>wKF)KJpZMo9)0ukOd-=V2>~su&Y&i1ZuuwB5=cA3?-Bs^Y`=7ytbJ)b|6VDTU2>YA^}@vQW6%_rAdBK?AUCo ztbgjie3bho8Jp_Qj7wdeL$cXcB|6NCKEbe>d16h&_PrjuA3l6_p8_;4Lbk!%aIa%t b-Jg1fgTa=ESVYZ09%t}$^>bP0l+XkK06u5+ literal 0 HcmV?d00001 diff --git a/api/src/main/resources/glyphs/dialogue/answer_button_active_2.png b/api/src/main/resources/glyphs/dialogue/answer_button_active_2.png new file mode 100644 index 0000000000000000000000000000000000000000..183942890cbf1ed5b92de80c43bd6b5348ad9d87 GIT binary patch literal 305 zcmeAS@N?(olHy`uVBq!ia0vp^8bB<FVdQ&MBb@04;ZW Al>h($ literal 0 HcmV?d00001 diff --git a/api/src/main/resources/glyphs/dialogue/answer_button_active_3.png b/api/src/main/resources/glyphs/dialogue/answer_button_active_3.png new file mode 100644 index 0000000000000000000000000000000000000000..065c30aecba16155900363c655d1cc3f04ac17e9 GIT binary patch literal 284 zcmeAS@N?(olHy`uVBq!ia0vp^8bB<gTe~DWM4fxO8p) literal 0 HcmV?d00001 diff --git a/api/src/main/resources/glyphs/dialogue/answer_button_active_4.png b/api/src/main/resources/glyphs/dialogue/answer_button_active_4.png new file mode 100644 index 0000000000000000000000000000000000000000..aee8169712ac4093ef3ee8bf80699c1dade716a3 GIT binary patch literal 301 zcmeAS@N?(olHy`uVBq!ia0vp^8bB<knn30@BfRkoXiq@Hf2w_?i8k4t9vx8q{M^XEz#zahE3qLtfVtS6p6IMI<5 z)#UizVNR&*&W5O$0&Hxx97$^zNJ=R5vS!G77JqnimVJsC@4;(4TTUK5ao~V~!dcyv uhOM?9`2}xIiO%`P$Ho4LY3I+6{~3SYa;m#87%vL+GJ~h9pUXO@geCybCVAZe literal 0 HcmV?d00001 diff --git a/api/src/main/resources/glyphs/dialogue/answer_not_active_text_field.png b/api/src/main/resources/glyphs/dialogue/answer_not_active_text_field.png new file mode 100644 index 0000000000000000000000000000000000000000..2e3329634b7adf404b60e6e5f3869904cbb5a3b0 GIT binary patch literal 442 zcmeAS@N?(olHy`uVBq!ia0vp^`+!)7gAGXDzQuGENHG=%xjQkeJ16rJ$S(18aSW-L z^Y)Id7jvM<(T~QHS|Ygg4mbrIS-X?n{DGgyR)e_@*mPWu@Ov!m$mTG1UGwh16V=pD z59a+}ztb|MOY`0CO5wC+mGzZTTm~P%|JL=qwYo;mL3HsCgZkQam2wCC-pI>%B|SPU z(#!8{VyE-9*M6SQ$}3+?O+J>ZiEl3mh`Dnut~2D7UY3N}RL zmrU)vzqR`ym*V8jPcN9)$+#>{TetB3t3NgSGD`&~oV;1I@#@Q4M;|B{Stj#ktJ_Vu zdaTo5%{oQRZjFere1XxdyZ1aMojdQRJ)diXwNFjNqK}E4$J$k;p`*7Vg}&C&KH_YG(dB{r=m|9jz{X1hgT0TE(+zInxd&^v74LPC9w}-1p7yp3?VL zJFBIiO?*51%8Dy%Out0Wo%~pt@uq0>+#iw)f{d>pIMO~f=@GLZ&_1c!nzO2&fsE`x chx}si@sSgnf0JnzFftfCUHx3vIVCg!0Po+*v;Y7A literal 0 HcmV?d00001 diff --git a/api/src/main/resources/glyphs/dialogue/avatar_frame.png b/api/src/main/resources/glyphs/dialogue/avatar_frame.png new file mode 100644 index 0000000000000000000000000000000000000000..4ba48057e087a17c455e76dee0f48d9ddad4d3cf GIT binary patch literal 648 zcmeAS@N?(olHy`uVBq!ia0vp^B_Pbf1|&mfiKzoA#^NA%Cx&(BWL`2bFgba;IEGZr zd3#6So4HV?;p6+1i)Jx`hEY7;e%#6O%$jNV74n5h`CU-Y(`d+@L z8@6xz_q=+}1-4zg>m@3y%rtr&6NA<-ym!}Zx-eIAfJE~l36Wz94A@!?K{7lf3$1hv z|E=9>Gtv2Ku@jnhNgLLuUw)`4DQzn+&x-E(bn=#RhP|zwo6C_$hiXD!2X@*#g;>7T zc2UgZ-8=VmamY9PSr2v3$7L%TC&ljVcsSQq9ppmi<#*;k-g6~-U5VkWIZ&tn*tqoJ zr0RLkr(f?Va4IxXH@SJY|7MtIb|B*%n9q2>eETPu|6YFOdP$f+eO?_qw1RuxviZaW zKzwV)f_E|LPqO_m!^e~N*4)=Abrn~?6oLFcIa`1G_n)EPtU?69@$vbNgz4AWS*drw zh}PO1i(M|vW&H8K^WTck(RYs@<^%@#nFmv5+nfzN`>f?q4YR@ymzX<}M}R4dfx*+& K&t;ucLK6TuP#Wm~ literal 0 HcmV?d00001 diff --git a/api/src/main/resources/glyphs/dialogue/background_1.png b/api/src/main/resources/glyphs/dialogue/background_1.png new file mode 100644 index 0000000000000000000000000000000000000000..d8d8e1fa9741a44fea943717c3ab4c5caddfd62e GIT binary patch literal 4893 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 P00000NkvXXu0mjfH)mbE literal 0 HcmV?d00001 diff --git a/api/src/main/resources/glyphs/dialogue/background_2.png b/api/src/main/resources/glyphs/dialogue/background_2.png new file mode 100644 index 0000000000000000000000000000000000000000..1f07f84d5075cc99d6443e1f3f6eabb07858d5c8 GIT binary patch literal 3909 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 P00000NkvXXu0mjfyx&GK literal 0 HcmV?d00001 diff --git a/api/src/main/resources/glyphs/dialogue/phrase_substrate_1.png b/api/src/main/resources/glyphs/dialogue/phrase_substrate_1.png new file mode 100644 index 0000000000000000000000000000000000000000..2c0af6f43eae5e70a41b5d2b5148472f09f5ac7c GIT binary patch literal 739 zcmeAS@N?(olHy`uVBq!ia0vp^n}E24gAGV3T4x*tQk(@Ik;M!Q+`=Ht$S`Y;1Oo%p zR8JSjkcv5P@7m^F36N+_^q*3uBP9hisqCm-1WWWuHV?;>Mk{`1+Hf~yEqeWNYYMN& zxAKa6&z|(g_!Pdocy`B$m(REB?`L0N!Czg^@F0G{iH-*wr=Iusudd~Luztgd4u^?? zjY`T)u5K({JscvEf@{Qn+Vstt{xSG>*RAg-R-8(j_}wmY&i%(?KqI=5D(==% zhEhe_PQ=fj-+tO8w$!3u`MGM%wiDAcXPM6ZtaWd%vi!>>h5L@~E`Opt*W}#xA8Ss$ zbq)$U^-ebORMyMBGs<^lXS(mO-qhn!o+3F>EYj`CBhk{`XSREv{TW#M@p#!BoU7FioT%Pi$+~&@|MY(&X zgns(+@^Slg*Si1j-ZF$7Q99kR)psZ~9aVZ?yUAkzaa|*&h}aZO?WMqg2l+i`;{82! zrgM&eTy;2gmW4jZXZ?@YyqpvM@ty2X5B1*9x^~e|zAaol2R$S+C#JeY!2+<=10H~1 zqagvE)n&?WQU2XNu=e0j638Pgg&ebxsLQ09(3B@c;k- literal 0 HcmV?d00001 diff --git a/api/src/main/resources/glyphs/dialogue/phrase_substrate_2.png b/api/src/main/resources/glyphs/dialogue/phrase_substrate_2.png new file mode 100644 index 0000000000000000000000000000000000000000..cf8a2b3fca149e51123b8276a449c9430d4d3e7a GIT binary patch literal 796 zcmeAS@N?(olHy`uVBq!ia0vp^n}E24gAGV3T4x*tQk(@Ik;M!Q+`=Ht$S`Y;1Oo%p zIZqeIkcv5P?;83^2a2!;99y*2e8QTG+6laIjC-xMtY*2GsQ3mg?ViHA#E83KmHios z=mm_H$%E?den|ggB z7x&(ol{-r`Jy|(;T5ry=GsoPP=}6z~nd!Uzes2Hkv(H}rEO=o*1~dG$Bv%WKTsUFZEy^(lTOQ{+DFby|6fr6T`M z<=LsvSDYx`-;?P#*KCudVf(J%w`c#GzW4I7)1M}NzWVEF<<(iWw@M=8s^nh1%g^sC zp05)*Z;HS9r#tH2)0RI=D{*fN(z`p+ck`{BI}@Mox+xp!ce2;os1#_h=H`tPSDtn= z3r+(%|9XM)_V&wdr;C>6069CYi-3k-B% zFc{B%_wU=YA2k*+Yt8lVD`}n<{j|Dw&%GSf5VrmK2uBESnRvD%Q+c-FWj8bBX92*- z`fa~|u37A~>2vD;{km(vK5Dc7Y`xRo=bi7?ZMQvFyD6p8t8VMW=XQ~sY)s7;14E?f zV_(j!tDu0pmVp#-*KWcC4#-3exa)`S|5>0V4Ng_{1`=x*>_2&SJ21I3c)I$ztaD0e F0su5|YB>M^ literal 0 HcmV?d00001 diff --git a/api/src/main/resources/glyphs/dialogue/scroll_phrase_down_button.png b/api/src/main/resources/glyphs/dialogue/scroll_phrase_down_button.png new file mode 100644 index 0000000000000000000000000000000000000000..ad906a1803a49faa659eeaff7321ad288cf56d49 GIT binary patch literal 246 zcmeAS@N?(olHy`uVBq!ia0vp^MnKHP!3HG1iSZT!DaPU;cPEB*=VV?2IU77(978O6 zw_dg7I^-bH^3Xg%o^|_0C3k^?%@Nhi?V1?M%TuC8PA@BUnN z@8^x>vsfIBrvJJjz5CUima@}I-w!@Lo}%~l^G%Z-`yFVQfwwE zIB+!nH}GQVP`r@9_E*pOdUoyR^;vw!C-X2~2$bFR^p_?7o}&>82N;;%N@%l4I6e?f u?qqQ0uu0YtbTC#+o#@VgYq`%g@e6m|GK zBXLRV!V~iv7=@i{+g_eFGq9=oJ5jAH=x#s`P%x^hZ57+2&n!F_P8o+7`@~K8zkf;x zL-Upu3XWU-