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 0000000..2f3bd34 Binary files /dev/null and b/api/src/main/resources/glyphs/dialogue/answer_active_text_field.png differ diff --git a/api/src/main/resources/glyphs/dialogue/answer_button_active_1.png b/api/src/main/resources/glyphs/dialogue/answer_button_active_1.png new file mode 100644 index 0000000..bb38d81 Binary files /dev/null and b/api/src/main/resources/glyphs/dialogue/answer_button_active_1.png differ 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 0000000..1839428 Binary files /dev/null and b/api/src/main/resources/glyphs/dialogue/answer_button_active_2.png differ 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 0000000..065c30a Binary files /dev/null and b/api/src/main/resources/glyphs/dialogue/answer_button_active_3.png differ 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 0000000..aee8169 Binary files /dev/null and b/api/src/main/resources/glyphs/dialogue/answer_button_active_4.png differ 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 0000000..2e33296 Binary files /dev/null and b/api/src/main/resources/glyphs/dialogue/answer_not_active_text_field.png differ 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 0000000..4ba4805 Binary files /dev/null and b/api/src/main/resources/glyphs/dialogue/avatar_frame.png differ 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 0000000..d8d8e1f Binary files /dev/null and b/api/src/main/resources/glyphs/dialogue/background_1.png differ 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 0000000..1f07f84 Binary files /dev/null and b/api/src/main/resources/glyphs/dialogue/background_2.png differ diff --git a/api/src/main/resources/glyphs/dialogue/background_3.png b/api/src/main/resources/glyphs/dialogue/background_3.png new file mode 100644 index 0000000..a8fffc2 Binary files /dev/null and b/api/src/main/resources/glyphs/dialogue/background_3.png differ diff --git a/api/src/main/resources/glyphs/dialogue/background_4.png b/api/src/main/resources/glyphs/dialogue/background_4.png new file mode 100644 index 0000000..d721a03 Binary files /dev/null and b/api/src/main/resources/glyphs/dialogue/background_4.png differ diff --git a/api/src/main/resources/glyphs/dialogue/default_avatar.png b/api/src/main/resources/glyphs/dialogue/default_avatar.png new file mode 100644 index 0000000..a35be4f Binary files /dev/null and b/api/src/main/resources/glyphs/dialogue/default_avatar.png differ 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 0000000..2c0af6f Binary files /dev/null and b/api/src/main/resources/glyphs/dialogue/phrase_substrate_1.png differ 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 0000000..cf8a2b3 Binary files /dev/null and b/api/src/main/resources/glyphs/dialogue/phrase_substrate_2.png differ 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 0000000..ad906a1 Binary files /dev/null and b/api/src/main/resources/glyphs/dialogue/scroll_phrase_down_button.png differ diff --git a/api/src/main/resources/glyphs/dialogue/scroll_phrase_up_button.png b/api/src/main/resources/glyphs/dialogue/scroll_phrase_up_button.png new file mode 100644 index 0000000..fab7ef4 Binary files /dev/null and b/api/src/main/resources/glyphs/dialogue/scroll_phrase_up_button.png differ