From dd4e1b17f1ee218b4b44104a20a58ac0df354aa1 Mon Sep 17 00:00:00 2001 From: ScarletRedMan Date: Sun, 1 Dec 2024 16:50:47 +0700 Subject: [PATCH] feat: implemented themes for monologues --- .../msb3/api/resource/MonologueResources.java | 101 ++++++++++++++ .../api/resource/ResourcePackManager.java | 5 +- .../msb3/api/talk/MonologueRenderer.java | 128 ------------------ .../ru/dragonestia/msb3/api/talk/Themes.java | 21 +++ .../msb3/api/talk/monologue/Monologue.java | 92 +++++++++++++ .../api/talk/monologue/MonologueTheme.java | 125 +++++++++++++++++ .../msb3/api/util/ResourceFromJar.java | 5 + 7 files changed, 347 insertions(+), 130 deletions(-) create mode 100644 api/src/main/java/ru/dragonestia/msb3/api/resource/MonologueResources.java delete mode 100644 api/src/main/java/ru/dragonestia/msb3/api/talk/MonologueRenderer.java create mode 100644 api/src/main/java/ru/dragonestia/msb3/api/talk/Themes.java create mode 100644 api/src/main/java/ru/dragonestia/msb3/api/talk/monologue/Monologue.java create mode 100644 api/src/main/java/ru/dragonestia/msb3/api/talk/monologue/MonologueTheme.java diff --git a/api/src/main/java/ru/dragonestia/msb3/api/resource/MonologueResources.java b/api/src/main/java/ru/dragonestia/msb3/api/resource/MonologueResources.java new file mode 100644 index 0000000..4a1c426 --- /dev/null +++ b/api/src/main/java/ru/dragonestia/msb3/api/resource/MonologueResources.java @@ -0,0 +1,101 @@ +package ru.dragonestia.msb3.api.resource; + +import net.kyori.adventure.key.Key; +import org.jetbrains.annotations.NotNull; +import ru.dragonestia.msb3.api.ServerBootstrap; +import ru.dragonestia.msb3.api.glyph.glyph.image.ImageGlyph; +import ru.dragonestia.msb3.api.glyph.glyph.image.TextureProperties; +import ru.dragonestia.msb3.api.glyph.pack.GlyphResourcePack; +import ru.dragonestia.msb3.api.glyph.pack.ResourceIdentifier; +import ru.dragonestia.msb3.api.glyph.pack.StringIdentifier; +import ru.dragonestia.msb3.api.util.ResourceFromJar; +import team.unnamed.creative.base.Writable; +import team.unnamed.creative.texture.Texture; + +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +public class MonologueResources { + + private static final Key MONOLOG_FONT_KEY = Key.key("msb3", "monolog"); + public static final String DEFAULT = "default"; + + private final GlyphResourcePack glyphResourcePack; + private final Map avatars = new HashMap<>(); + private final Map frames = new HashMap<>(); + private final GlyphEntry speechIndicator = initSpeechIndicator(); + + public MonologueResources(GlyphResourcePack glyphResourcePack) { + this.glyphResourcePack = glyphResourcePack; + + registerAvatar(DEFAULT, ResourceFromJar.of(ServerBootstrap.CLASS_LOADER, "glyphs/monologue/default_avatar.png")); + registerFrame(DEFAULT, ResourceFromJar.of(ServerBootstrap.CLASS_LOADER, "glyphs/monologue/avatar_frame.png")); + } + + private GlyphEntry initSpeechIndicator() { + var writable = ResourceFromJar.of(ServerBootstrap.CLASS_LOADER, "glyphs/monologue/speech_indicator.png"); + var glyph = ImageGlyph.of(MONOLOG_FONT_KEY, + Texture.texture().key(Key.key("msb3", "monolog/speech_indicator.png")).data(writable).build(), + new TextureProperties(8, 6)); + var glyphIdentifier = StringIdentifier.image("monolog_speech_indicator"); + + return new GlyphEntry(glyphIdentifier, glyph); + } + + public void registerAvatar(String identifier, Writable writable) { + var glyph = ImageGlyph.of(MONOLOG_FONT_KEY, + Texture.texture().key(Key.key("msb3", "monolog/avatar_" + identifier + ".png")).data(writable).build(), + new TextureProperties(42, 2)); + + var glyphIdentifier = StringIdentifier.image("monolog_avatar_" + identifier); + + avatars.put(identifier, new GlyphEntry(glyphIdentifier, glyph)); + + glyphResourcePack.with(glyphIdentifier, glyph); + } + + public void registerFrame(String identifier, Writable writable) { + var glyph = ImageGlyph.of(MONOLOG_FONT_KEY, + Texture.texture().key(Key.key("msb3", "monolog/frame_" + identifier + ".png")).data(writable).build(), + new TextureProperties(50, 6)); + + var glyphIdentifier = StringIdentifier.image("monolog_frame_" + identifier); + + frames.put(identifier, new GlyphEntry(glyphIdentifier, glyph)); + + glyphResourcePack.with(glyphIdentifier, glyph); + } + + public Optional getAvatar(String identifier) { + return Optional.ofNullable(avatars.get(identifier)) + .map(entry -> { + try { + return glyphResourcePack.get(entry.identifier()); + } catch (IllegalArgumentException ex) { + return null; + } + }); + } + + public Optional getFrame(String identifier) { + return Optional.ofNullable(frames.get(identifier)) + .map(entry -> { + try { + return glyphResourcePack.get(entry.identifier()); + } catch (IllegalArgumentException ex) { + return null; + } + }); + } + + public ImageGlyph getSpeechIndicator() { + return glyphResourcePack.get(speechIndicator.identifier()); + } + + public void compile(GlyphResourcePack resourcePack) { + resourcePack.with(speechIndicator.identifier(), speechIndicator.glyph()); + } + + private record GlyphEntry(ResourceIdentifier<@NotNull ImageGlyph> identifier, ImageGlyph glyph) {} +} 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 7aff414..85fcfdf 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,7 +7,6 @@ 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.MonologueRenderer; import ru.dragonestia.msb3.api.title.BlackScreen; import team.unnamed.creative.ResourcePack; import team.unnamed.creative.atlas.Atlas; @@ -26,6 +25,7 @@ public class ResourcePackManager { private final ResourcePack resourcePack; private final GlyphResourcePack glyphResourcePack; + private final MonologueResources monologueResources; public ResourcePackManager() { resourcePack = ResourcePack.resourcePack(); @@ -34,6 +34,7 @@ public class ResourcePackManager { resourcePack.unknownFile("credits.txt", Writable.stringUtf8("dragonestia.ru")); glyphResourcePack = GlyphResourcePack.create(); + monologueResources = new MonologueResources(glyphResourcePack); initDefaultGlyphs(); } @@ -42,7 +43,7 @@ public class ResourcePackManager { glyphResourcePack.with(BlankSlotItem.compile()); glyphResourcePack.with(GlyphCompiler.instance().compile(BlackScreen.GLYPH)); glyphResourcePack.with(GlyphFont.compile()); - MonologueRenderer.compile(glyphResourcePack); + monologueResources.compile(glyphResourcePack); } public void compile() { diff --git a/api/src/main/java/ru/dragonestia/msb3/api/talk/MonologueRenderer.java b/api/src/main/java/ru/dragonestia/msb3/api/talk/MonologueRenderer.java deleted file mode 100644 index a7f405d..0000000 --- a/api/src/main/java/ru/dragonestia/msb3/api/talk/MonologueRenderer.java +++ /dev/null @@ -1,128 +0,0 @@ -package ru.dragonestia.msb3.api.talk; - -import net.kyori.adventure.key.Key; -import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.format.NamedTextColor; -import net.kyori.adventure.text.format.Style; -import net.kyori.adventure.text.format.TextDecoration; -import net.minestom.server.entity.Player; -import org.jetbrains.annotations.NotNull; -import ru.dragonestia.msb3.api.ServerBootstrap; -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.pack.GlyphResourcePack; -import ru.dragonestia.msb3.api.glyph.pack.ResourceIdentifier; -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; - -public final class MonologueRenderer { - - private static final Key MONOLOG_FONT_KEY = Key.key("msb3", "monolog"); - private static final Writable DEFAULT_COMPANION_AVATAR_WRITABLE = ResourceFromJar.of(ServerBootstrap.CLASS_LOADER, "glyphs/monologue/default_avatar.png"); - private static final Writable COMPANION_AVATAR_FRAME_WRITABLE = ResourceFromJar.of(ServerBootstrap.CLASS_LOADER, "glyphs/monologue/avatar_frame.png"); - private static final Writable SPEECH_INDICATOR_WRITABLE = ResourceFromJar.of(ServerBootstrap.CLASS_LOADER, "glyphs/monologue/speech_indicator.png"); - private static final Component INDENT_COMPONENT = Component.text(" ".repeat(14)); - private static final ImageGlyph AVATAR_FRAME; - private static final ImageGlyph DEFAULT_AVATAR; - private static final ImageGlyph SPEECH_INDICATOR; - private static final ResourceIdentifier<@NotNull ImageGlyph> AVATAR_FRAME_RESOURCE_IDENTIFIER; - private static final ResourceIdentifier<@NotNull ImageGlyph> SPEECH_INDICATOR_RESOURCE_IDENTIFIER; - private static final ResourceIdentifier<@NotNull ImageGlyph> DEFAULT_AVATAR_RESOURCE_IDENTIFIER; - - static { - AVATAR_FRAME = ImageGlyph.of(MONOLOG_FONT_KEY, - Texture.texture().key(Key.key("msb3", "monolog/avatar_frame.png")).data(COMPANION_AVATAR_FRAME_WRITABLE).build(), - new TextureProperties(50, 6)); - - DEFAULT_AVATAR = ImageGlyph.of(MONOLOG_FONT_KEY, - Texture.texture().key(Key.key("msb3", "monolog/default_avatar.png")).data(DEFAULT_COMPANION_AVATAR_WRITABLE).build(), - new TextureProperties(42, 2)); - - SPEECH_INDICATOR = ImageGlyph.of(MONOLOG_FONT_KEY, - Texture.texture().key(Key.key("msb3", "monolog/speech_indicator.png")).data(SPEECH_INDICATOR_WRITABLE).build(), - new TextureProperties(8, 6)); - - AVATAR_FRAME_RESOURCE_IDENTIFIER = StringIdentifier.image("monolog_avatar_frame"); - SPEECH_INDICATOR_RESOURCE_IDENTIFIER = StringIdentifier.image("monolog_speech_indicator"); - DEFAULT_AVATAR_RESOURCE_IDENTIFIER = StringIdentifier.image("monolog_default_avatar"); - } - - private final Player player; - private final String title; - private final String message; - - private MonologueRenderer(Player player, String title, String message) { - this.player = player; - this.title = title; - this.message = message; - } - - public void show() { - player.sendMessage(toComponent(ServerBootstrap.getInstance().getResourcePackManager().getGlyphResourcePack())); - } - - public static MonologueRenderer create(Player player, String title, String message) { - return new MonologueRenderer(player, title, message); - } - - private Component toComponent(@NotNull GlyphResourcePack resourcePack) { - Component text = Component.text(message); - Component[] parts = StringUtil.splitIntoParts(text, 180, string -> string.length() * 4); - if (parts != null && parts.length != 0) { - int avatarLineStart = Math.max(parts.length - 4, 0) / 2; - if (parts.length > 3) ++avatarLineStart; - - var avatarComponent = GlyphComponentBuilder.universal(resourcePack.spaces()) - .append(resourcePack.get(AVATAR_FRAME_RESOURCE_IDENTIFIER)) - .append(4, resourcePack.get(DEFAULT_AVATAR_RESOURCE_IDENTIFIER)) - .build(); - - var titleComponent = Component.empty() - .append(resourcePack.get(SPEECH_INDICATOR_RESOURCE_IDENTIFIER).toAdventure()) - .append(Component.space()).append(Component.text(title, NamedTextColor.WHITE, TextDecoration.BOLD)); - - var monolog = Component.text(); - - int lineIdx; - for(lineIdx = 0; lineIdx < parts.length + 2; ++lineIdx) { - if (lineIdx == avatarLineStart) { - monolog.append(avatarComponent); - } - - monolog.append(INDENT_COMPONENT); - if (lineIdx == 0) { - monolog.append(Component.newline()); - } - - if (lineIdx == 1) { - monolog.append(titleComponent).append(Component.newline()); - } - - if (lineIdx > 1) { - monolog.append(parts[lineIdx - 2].applyFallbackStyle(Style.style(NamedTextColor.GRAY))) - .append(Component.newline()); - } - } - - if (parts.length < 4) { - for(lineIdx = parts.length + 2; lineIdx < 5; ++lineIdx) { - monolog.append(Component.newline()); - } - } - - return monolog.asComponent(); - } else { - throw new IllegalArgumentException("Cannot fit speech text"); - } - } - - public static void compile(GlyphResourcePack resourcePack) { - resourcePack.with(DEFAULT_AVATAR_RESOURCE_IDENTIFIER, DEFAULT_AVATAR) - .with(SPEECH_INDICATOR_RESOURCE_IDENTIFIER, SPEECH_INDICATOR) - .with(AVATAR_FRAME_RESOURCE_IDENTIFIER, AVATAR_FRAME); - } -} diff --git a/api/src/main/java/ru/dragonestia/msb3/api/talk/Themes.java b/api/src/main/java/ru/dragonestia/msb3/api/talk/Themes.java new file mode 100644 index 0000000..0386188 --- /dev/null +++ b/api/src/main/java/ru/dragonestia/msb3/api/talk/Themes.java @@ -0,0 +1,21 @@ +package ru.dragonestia.msb3.api.talk; + +import lombok.experimental.UtilityClass; +import ru.dragonestia.msb3.api.talk.monologue.MonologueTheme; + +import java.util.HashMap; +import java.util.Map; + +@UtilityClass +public class Themes { + + private final Map monologueThemes = new HashMap<>(); + + public void registerMonologueTheme(String identifier, MonologueTheme theme) { + monologueThemes.put(identifier, theme); + } + + public MonologueTheme getMonologueTheme(String identifier) { + return monologueThemes.get(identifier); + } +} diff --git a/api/src/main/java/ru/dragonestia/msb3/api/talk/monologue/Monologue.java b/api/src/main/java/ru/dragonestia/msb3/api/talk/monologue/Monologue.java new file mode 100644 index 0000000..05beb8d --- /dev/null +++ b/api/src/main/java/ru/dragonestia/msb3/api/talk/monologue/Monologue.java @@ -0,0 +1,92 @@ +package ru.dragonestia.msb3.api.talk.monologue; + +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.Style; +import net.kyori.adventure.text.format.TextDecoration; +import net.minestom.server.entity.Player; +import ru.dragonestia.msb3.api.ServerBootstrap; +import ru.dragonestia.msb3.api.glyph.glyph.GlyphComponentBuilder; +import ru.dragonestia.msb3.api.util.StringUtil; + +import java.util.Objects; + +public final class Monologue { + + private static final Component INDENT_COMPONENT = Component.text(" ".repeat(14)); + + private final Player player; + private final String title; + private final String message; + + private Monologue(Player player, String title, String message) { + this.player = player; + this.title = title; + this.message = message; + } + + public void show() { + show(MonologueTheme.builder().build()); + } + + public void show(MonologueTheme theme) { + player.sendMessage(toComponent(theme)); + } + + public static Monologue create(Player player, String title, String message) { + return new Monologue(player, title, message); + } + + private Component toComponent(MonologueTheme theme) { + var spaces = ServerBootstrap.getInstance().getResourcePackManager().getGlyphResourcePack().spaces(); + var speech = ServerBootstrap.getInstance().getResourcePackManager().getMonologueResources().getSpeechIndicator(); + + Component text = Component.text(message); + Component[] parts = StringUtil.splitIntoParts(text, 180, string -> string.length() * 4); + if (parts != null && parts.length != 0) { + int avatarLineStart = Math.max(parts.length - 4, 0) / 2; + if (parts.length > 3) ++avatarLineStart; + + var avatarComponent = GlyphComponentBuilder.universal(spaces) + .append(Objects.requireNonNull(theme.getFrame())) + .append(4, Objects.requireNonNull(theme.getAvatar())) + .build(); + + var titleComponent = Component.empty() + .append(speech.toAdventure()) + .append(Component.space()).append(Component.text(title, theme.getTitleColor(), TextDecoration.BOLD)); + + var monolog = Component.text(); + + int lineIdx; + for(lineIdx = 0; lineIdx < parts.length + 2; ++lineIdx) { + if (lineIdx == avatarLineStart) { + monolog.append(avatarComponent); + } + + monolog.append(INDENT_COMPONENT); + if (lineIdx == 0) { + monolog.append(Component.newline()); + } + + if (lineIdx == 1) { + monolog.append(titleComponent).append(Component.newline()); + } + + if (lineIdx > 1) { + monolog.append(parts[lineIdx - 2].applyFallbackStyle(Style.style(theme.getTextColor()))) + .append(Component.newline()); + } + } + + if (parts.length < 4) { + for(lineIdx = parts.length + 2; lineIdx < 5; ++lineIdx) { + monolog.append(Component.newline()); + } + } + + return monolog.asComponent(); + } else { + throw new IllegalArgumentException("Cannot fit speech text"); + } + } +} diff --git a/api/src/main/java/ru/dragonestia/msb3/api/talk/monologue/MonologueTheme.java b/api/src/main/java/ru/dragonestia/msb3/api/talk/monologue/MonologueTheme.java new file mode 100644 index 0000000..9488bf7 --- /dev/null +++ b/api/src/main/java/ru/dragonestia/msb3/api/talk/monologue/MonologueTheme.java @@ -0,0 +1,125 @@ +package ru.dragonestia.msb3.api.talk.monologue; + +import lombok.AccessLevel; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.extern.log4j.Log4j2; +import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.format.TextColor; +import ru.dragonestia.msb3.api.ServerBootstrap; +import ru.dragonestia.msb3.api.glyph.glyph.image.ImageGlyph; +import ru.dragonestia.msb3.api.resource.MonologueResources; +import ru.dragonestia.msb3.api.resource.ResourcePackManager; + +import java.util.Objects; + +@Log4j2 +@Getter +@RequiredArgsConstructor(access = AccessLevel.PRIVATE) +public class MonologueTheme { + + private final ResourcePackManager resourcePackManager = ServerBootstrap.getInstance().getResourcePackManager(); + private final ImageGlyph avatar; + private final ImageGlyph frame; + private final TextColor titleColor; + private final TextColor textColor; + + public MonologueTheme withAvatar(String identifier) { + var opt = resourcePackManager.getMonologueResources().getAvatar(identifier); + if (opt.isPresent()) { + return withAvatar(opt.get()); + } + + log.warn("No avatar found for identifier '{}'. Using default", identifier); + withAvatar(MonologueResources.DEFAULT); + return this; + } + + public MonologueTheme withAvatar(ImageGlyph avatar) { + return new MonologueTheme(avatar, frame, titleColor, textColor); + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + + private final ResourcePackManager resourcePackManager; + + private ImageGlyph avatar = null; + private ImageGlyph frame = null; + private TextColor titleColor = null; + private TextColor textColor = null; + + private Builder() { + resourcePackManager = ServerBootstrap.getInstance().getResourcePackManager(); + } + + public MonologueTheme build() { + if (avatar == null) { + setAvatar(MonologueResources.DEFAULT); + if (avatar == null) { + throw new IllegalStateException("Trying to get avatar before resource pack initialization"); + } + } + if (frame == null) { + setFrame(MonologueResources.DEFAULT); + if (frame == null) { + throw new IllegalStateException("Trying to get frame before resource pack initialization"); + } + } + if (titleColor == null) setTitleColor(NamedTextColor.WHITE); + if (textColor == null) setTextColor(NamedTextColor.GRAY); + + return new MonologueTheme( + avatar, + frame, + titleColor, + textColor + ); + } + + public Builder setAvatar(ImageGlyph avatar) { + this.avatar = avatar; + return this; + } + + public Builder setFrame(ImageGlyph frame) { + this.frame = frame; + return this; + } + + public Builder setAvatar(String identifier) { + var opt = resourcePackManager.getMonologueResources().getAvatar(identifier); + if (opt.isPresent()) { + avatar = Objects.requireNonNull(opt.get()); + return this; + } + + log.warn("No avatar found for identifier '{}'. Using default", identifier); + return this; + } + + public Builder setFrame(String identifier) { + var opt = resourcePackManager.getMonologueResources().getFrame(identifier); + if (opt.isPresent()) { + frame = Objects.requireNonNull(opt.get()); + return this; + } + + log.warn("No frame found for identifier '{}'. Using default", identifier); + return this; + } + + public Builder setTitleColor(TextColor titleColor) { + this.titleColor = titleColor; + return this; + } + + public Builder setTextColor(TextColor textColor) { + this.textColor = textColor; + return this; + } + } +} diff --git a/api/src/main/java/ru/dragonestia/msb3/api/util/ResourceFromJar.java b/api/src/main/java/ru/dragonestia/msb3/api/util/ResourceFromJar.java index 204829d..ef9c363 100644 --- a/api/src/main/java/ru/dragonestia/msb3/api/util/ResourceFromJar.java +++ b/api/src/main/java/ru/dragonestia/msb3/api/util/ResourceFromJar.java @@ -1,5 +1,6 @@ package ru.dragonestia.msb3.api.util; +import ru.dragonestia.msb3.api.ServerBootstrap; import team.unnamed.creative.base.Writable; import java.io.IOException; @@ -8,6 +9,10 @@ import java.net.URLConnection; public interface ResourceFromJar { + static Writable of(String fileName) { + return of(ServerBootstrap.CLASS_LOADER, fileName); + } + static Writable of(ClassLoader classLoader, String fileName) { return Writable.inputStream(() -> { try {