feat: implemented themes for monologues

This commit is contained in:
Andrey Terentev 2024-12-01 16:50:47 +07:00
parent f46ce9b5be
commit dd4e1b17f1
7 changed files with 347 additions and 130 deletions

View File

@ -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<String, GlyphEntry> avatars = new HashMap<>();
private final Map<String, GlyphEntry> 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<ImageGlyph> getAvatar(String identifier) {
return Optional.ofNullable(avatars.get(identifier))
.map(entry -> {
try {
return glyphResourcePack.get(entry.identifier());
} catch (IllegalArgumentException ex) {
return null;
}
});
}
public Optional<ImageGlyph> 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) {}
}

View File

@ -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() {

View File

@ -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);
}
}

View File

@ -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<String, MonologueTheme> monologueThemes = new HashMap<>();
public void registerMonologueTheme(String identifier, MonologueTheme theme) {
monologueThemes.put(identifier, theme);
}
public MonologueTheme getMonologueTheme(String identifier) {
return monologueThemes.get(identifier);
}
}

View File

@ -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");
}
}
}

View File

@ -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;
}
}
}

View File

@ -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 {