diff --git a/.gitignore b/.gitignore index 0bc1acb..8b16239 100644 --- a/.gitignore +++ b/.gitignore @@ -24,4 +24,6 @@ bin/ .vscode/ .DS_Store -run/ \ No newline at end of file +run/ + +tarkov/ \ No newline at end of file diff --git a/api/build.gradle b/api/build.gradle index 52ba959..6531284 100644 --- a/api/build.gradle +++ b/api/build.gradle @@ -1,16 +1,42 @@ +plugins { + id 'com.github.johnrengelman.shadow' version '7.0.0' +} + +tasks { + shadowJar { + manifest { + attributes ( + "Multi-Release": true, + "Add-Opens": "java.base/java.lang " + + "java.base/java.lang.reflect" + ) + } + + mergeServiceFiles() + } + + java { + targetCompatibility = JavaVersion.VERSION_21 + } + + assemble { + dependsOn shadowJar, processResources + } +} + dependencies { - api 'net.minestom:minestom-snapshots:bb7acc2e77' + api project(":resource-compiler") + + api 'net.minestom:minestom-snapshots:1_21_4-6490538291' api 'org.slf4j:slf4j-api:2.0.16' api 'org.apache.logging.log4j:log4j-slf4j2-impl:2.24.0' api 'org.apache.logging.log4j:log4j-api:2.24.0' api 'org.apache.logging.log4j:log4j-core:2.19.0' - api 'net.kyori:adventure-api:4.17.0' - api 'net.kyori:adventure-text-minimessage:4.17.0' - api 'team.unnamed:creative-api:1.7.3' - api 'team.unnamed:creative-serializer-minecraft:1.7.3' - api 'team.unnamed:creative-server:1.7.3' + api 'org.spongepowered:configurate-hocon:4.2.0' + api 'org.spongepowered:configurate-gson:4.2.0' + api 'org.spongepowered:configurate-yaml:4.2.0' api 'org.sql2o:sql2o:1.8.0' api 'com.clickhouse:clickhouse-jdbc:0.7.1' @@ -19,4 +45,6 @@ dependencies { api 'io.prometheus:prometheus-metrics-core:1.3.1' api 'io.prometheus:prometheus-metrics-instrumentation-jvm:1.3.1' api 'io.prometheus:prometheus-metrics-exporter-httpserver:1.3.1' + + implementation 'org.apache.commons:commons-text:1.10.0' } diff --git a/api/src/main/java/ru/dragonestia/msb3/api/Bootstrap.java b/api/src/main/java/ru/dragonestia/msb3/api/Bootstrap.java deleted file mode 100644 index 480d536..0000000 --- a/api/src/main/java/ru/dragonestia/msb3/api/Bootstrap.java +++ /dev/null @@ -1,22 +0,0 @@ -package ru.dragonestia.msb3.api; - -import lombok.extern.log4j.Log4j2; -import net.minestom.server.entity.GameMode; -import ru.dragonestia.msb3.api.module.FlatWorldModule; -import ru.dragonestia.msb3.api.module.MotdModule; -import ru.dragonestia.msb3.api.module.ResourcePackRepositoryModule; - -@Log4j2 -public class Bootstrap { - - public static void main(String[] args) { - var boot = new ServerBootstrap(); - - MotdModule.init("logo.png", "msb3 server"); - FlatWorldModule.init(GameMode.ADVENTURE); - - ResourcePackRepositoryModule.init(boot, "0.0.0.0", 7270); - - boot.start("0.0.0.0", 25565); - } -} diff --git a/api/src/main/java/ru/dragonestia/msb3/api/ServerBootstrap.java b/api/src/main/java/ru/dragonestia/msb3/api/ServerBootstrap.java deleted file mode 100644 index b34d08d..0000000 --- a/api/src/main/java/ru/dragonestia/msb3/api/ServerBootstrap.java +++ /dev/null @@ -1,60 +0,0 @@ -package ru.dragonestia.msb3.api; - -import lombok.Getter; -import lombok.extern.log4j.Log4j2; -import net.minestom.server.MinecraftServer; -import ru.dragonestia.msb3.api.item.ItemPrefabManager; -import ru.dragonestia.msb3.api.item.ItemUtil; -import ru.dragonestia.msb3.api.resource.ResourcePackManager; - -import java.net.InetSocketAddress; - -@Log4j2 -public class ServerBootstrap { - - public static final ClassLoader CLASS_LOADER = ServerBootstrap.class.getClassLoader(); - - private static final String[] LOGO = { - "==============================================", - "Minestom Server Base v3 - by ScarletRedMan", - "", - " 8b d8 .dP\"Y8 88\"\"Yb 88888 ", - " 88b d88 `Ybo.\" 88__dP .dP ", - " 88YbdP88 o.`Y8b 88\"\"Yb o `Yb ", - " 88 YY 88 8bodP' 88oodP YbodP ", - "", - "Minecraft version: " + MinecraftServer.VERSION_NAME + " (protocol: " + MinecraftServer.PROTOCOL_VERSION + ")", - "==============================================", - }; - - @Getter private static ServerBootstrap instance; - @Getter private static boolean started = false; - - private final MinecraftServer server; - @Getter private final ResourcePackManager resourcePackManager; - @Getter private final ItemPrefabManager itemPrefabManager; - - public ServerBootstrap() { - instance = this; - - for (var line: LOGO) log.info(line); - - server = MinecraftServer.init(); - - MinecraftServer.setBrandName("Dragonestia"); - - resourcePackManager = new ResourcePackManager(); - itemPrefabManager = new ItemPrefabManager(); - - init(); - } - - public void start(String address, int port) { - started = true; - server.start(new InetSocketAddress(address, port)); - } - - private void init() { - ItemUtil.init(); - } -} diff --git a/api/src/main/java/ru/dragonestia/msb3/api/boot/DefaultBootstrap.java b/api/src/main/java/ru/dragonestia/msb3/api/boot/DefaultBootstrap.java new file mode 100644 index 0000000..edc547a --- /dev/null +++ b/api/src/main/java/ru/dragonestia/msb3/api/boot/DefaultBootstrap.java @@ -0,0 +1,36 @@ +package ru.dragonestia.msb3.api.boot; + +import lombok.extern.log4j.Log4j2; +import net.minestom.server.entity.GameMode; +import ru.dragonestia.msb3.api.module.FlatWorldModule; +import ru.dragonestia.msb3.api.module.MotdModule; +import ru.dragonestia.msb3.api.module.PrometheusMetricsModule; +import ru.dragonestia.msb3.api.module.ResourcePackRepositoryModule; +import team.unnamed.creative.ResourcePack; + +import java.net.InetSocketAddress; + +@Log4j2 +public class DefaultBootstrap extends ServerInitializer { + + public static void main(String[] args) { + ServerBootstrap.start("0.0.0.0", 25565, new DefaultBootstrap()); + } + + @Override + public void onDefaultModulesLoaded() { + MotdModule.init("logo.png", "msb3 server"); + FlatWorldModule.init(GameMode.ADVENTURE); + PrometheusMetricsModule.init(new InetSocketAddress("0.0.0.0", 7500)); + } + + @Override + public void onInitializeResources(ResourcePack resourcePack) { + + } + + @Override + public void onResourcePackCompiled(ResourcePack resourcePack) { + ResourcePackRepositoryModule.init("0.0.0.0", 7270); + } +} diff --git a/api/src/main/java/ru/dragonestia/msb3/api/boot/ServerBootstrap.java b/api/src/main/java/ru/dragonestia/msb3/api/boot/ServerBootstrap.java new file mode 100644 index 0000000..c687246 --- /dev/null +++ b/api/src/main/java/ru/dragonestia/msb3/api/boot/ServerBootstrap.java @@ -0,0 +1,207 @@ +package ru.dragonestia.msb3.api.boot; + +import lombok.Getter; +import lombok.experimental.UtilityClass; +import lombok.extern.log4j.Log4j2; +import net.kyori.adventure.key.Key; +import net.minestom.server.MinecraftServer; +import net.minestom.server.event.player.AsyncPlayerConfigurationEvent; +import net.minestom.server.event.player.PlayerDisconnectEvent; +import net.minestom.server.event.player.PlayerSpawnEvent; +import ru.dragonestia.msb3.api.command.DebugCommands; +import ru.dragonestia.msb3.api.dialog.DialogRegistry; +import ru.dragonestia.msb3.api.dialog.action.CloseDialogActionHandler; +import ru.dragonestia.msb3.api.dialog.action.DialogDialogActionHandler; +import ru.dragonestia.msb3.api.dialog.condition.AlwaysDialogConditionHandler; +import ru.dragonestia.msb3.api.dialog.condition.NeverDialogConditionHandler; +import ru.dragonestia.msb3.api.entity.PickableItem; +import ru.dragonestia.msb3.api.event.NPCClickEvent; +import ru.dragonestia.msb3.api.item.ItemUtil; +import ru.dragonestia.msb3.api.player.MsbPlayer; +import ru.dragonestia.msb3.api.player.PlayerContextManager; +import ru.dragonestia.msb3.api.player.defaults.KeyedBossBarContext; +import ru.dragonestia.msb3.api.player.defaults.NavigatorContext; +import ru.dragonestia.msb3.api.player.defaults.TalksContext; +import ru.dragonestia.msb3.api.resource.DialogueResources; +import ru.dragonestia.msb3.api.resource.MonologueResources; +import ru.dragonestia.msb3.api.skin.SkinStorage; +import ru.dragonestia.msb3.api.ui.BlackScreen; +import ru.dragonestia.msb3.api.ui.bossbar.KeyedBossBars; +import ru.dragonestia.msb3.api.ui.navigator.Navigators; +import ru.dragonestia.msb3.api.util.ResourceFromJar; +import ru.dragonestia.msb3.resource.Resources; +import ru.dragonestia.msb3.resource.glyph.GlyphCharacterFactory; +import ru.dragonestia.msb3.resource.glyph.MinecraftFont; +import ru.dragonestia.msb3.resource.utils.ClassPreLoader; +import team.unnamed.creative.lang.Language; +import team.unnamed.creative.serialize.minecraft.MinecraftResourcePackWriter; + +import java.io.File; +import java.net.InetSocketAddress; +import java.util.HashMap; +import java.util.List; + +@Log4j2 +@UtilityClass +public final class ServerBootstrap { + + public static final ClassLoader CLASS_LOADER = ServerBootstrap.class.getClassLoader(); + + private static final String[] LOGO = { + "==============================================", + "Minestom Server Base v3 - by ScarletRedMan", + "", + " 8b d8 .dP\"Y8 88\"\"Yb 88888 ", + " 88b d88 `Ybo.\" 88__dP .dP ", + " 88YbdP88 o.`Y8b 88\"\"Yb o `Yb ", + " 88 YY 88 8bodP' 88oodP YbodP ", + "", + "Minecraft version: " + MinecraftServer.VERSION_NAME + " (protocol: " + MinecraftServer.PROTOCOL_VERSION + ")", + "==============================================", + }; + + @Getter private boolean started = false; + + private final MinecraftServer server = preInitialize(); + + private MinecraftServer preInitialize() { + for (var line: LOGO) log.info(line); + + var server = MinecraftServer.init(); + MinecraftServer.setBrandName("Dragonestia"); + return server; + } + + public synchronized void start(String address, int port, ServerInitializer initializer) { + if (started) return; + + started = true; + init(initializer); + server.start(new InetSocketAddress(address, port)); + initializer.onServerStarted(); + } + + private void init(ServerInitializer initializer) { + initializer.onLoad(); + + initDefaultModules(); + initializer.onDefaultModulesLoaded(); + + initDefaultGlyphs(); + initializer.onInitializeResources(Resources.getResourcePack()); + + compileResourcePack(); + initializer.onResourcePackCompiled(Resources.getResourcePack()); + } + + private void initDefaultModules() { + ItemUtil.init(); + PickableItem.registerEvent(); + initPlayerContextManager(); + initDefaultSkins(); + initDefaultDialogActionsAndConditions(); + NPCClickEvent.init(); + DebugCommands.init(); + } + + private void compileResourcePack() { + var resourcePack = Resources.compile(); + + log.info("Used {}/{} glyphs", GlyphCharacterFactory.countUsedChars(), GlyphCharacterFactory.countCharRange()); + + var file = new File("./resource-pack.zip"); + MinecraftResourcePackWriter.minecraft().writeToZipFile(file, resourcePack); + + log.info("Compiled resource pack. File: ./resource-pack.zip"); + } + + private void initDefaultGlyphs() { + ClassPreLoader.preload(BlackScreen.class); + ClassPreLoader.preload(Navigators.class); + ClassPreLoader.preload(KeyedBossBars.class); + + MonologueResources.registerAvatar(MonologueResources.DEFAULT, ResourceFromJar.of(ServerBootstrap.CLASS_LOADER, "glyphs/monologue/default_avatar.png")); + MonologueResources.registerFrame(MonologueResources.DEFAULT, ResourceFromJar.of(ServerBootstrap.CLASS_LOADER, "glyphs/monologue/avatar_frame.png")); + + DialogueResources.registerAvatar(DialogueResources.DEFAULT, ResourceFromJar.of("glyphs/dialogue/default_avatar.png")); + DialogueResources.registerAvatarFrame(DialogueResources.DEFAULT, ResourceFromJar.of("glyphs/dialogue/avatar_frame.png")); + DialogueResources.registerScrollUp(DialogueResources.DEFAULT, ResourceFromJar.of("glyphs/dialogue/scroll_phrase_up_button.png")); + DialogueResources.registerScrollDown(DialogueResources.DEFAULT, ResourceFromJar.of("glyphs/dialogue/scroll_phrase_down_button.png")); + DialogueResources.registerBackground(DialogueResources.DEFAULT, ResourceFromJar.of("glyphs/dialogue/background.png")); + DialogueResources.registerSubstrate(DialogueResources.DEFAULT, ResourceFromJar.of("glyphs/dialogue/phrase_substrate.png")); + DialogueResources.registerActiveTextField(DialogueResources.DEFAULT, ResourceFromJar.of("glyphs/dialogue/answer_active_text_field.png")); + DialogueResources.registerNotActiveTextField(DialogueResources.DEFAULT, ResourceFromJar.of("glyphs/dialogue/answer_not_active_text_field.png")); + DialogueResources.registerButton(DialogueResources.DEFAULT + 1, ResourceFromJar.of("glyphs/dialogue/answer_button_active_1.png")); + DialogueResources.registerButton(DialogueResources.DEFAULT + 2, ResourceFromJar.of("glyphs/dialogue/answer_button_active_2.png")); + DialogueResources.registerButton(DialogueResources.DEFAULT + 3, ResourceFromJar.of("glyphs/dialogue/answer_button_active_3.png")); + DialogueResources.registerButton(DialogueResources.DEFAULT + 4, ResourceFromJar.of("glyphs/dialogue/answer_button_active_4.png")); + initDialogueFont(); + initLocales(); + } + + private void initDialogueFont() { + MinecraftFont.compileFontProvider(8, -28); + MinecraftFont.compileFontProvider(8, -37); + MinecraftFont.compileFontProvider(8, -46); + MinecraftFont.compileFontProvider(8, -55); + MinecraftFont.compileFontProvider(8, -64); + MinecraftFont.compileFontProvider(8, -73); + MinecraftFont.compileFontProvider(8, -82); + + MinecraftFont.compileFontProvider(8, -122); + MinecraftFont.compileFontProvider(8, -131); + MinecraftFont.compileFontProvider(8, -140); + MinecraftFont.compileFontProvider(8, -149); + + MinecraftFont.compileFontProvider(8, -170); + MinecraftFont.compileFontProvider(8, -179); + MinecraftFont.compileFontProvider(8, -188); + MinecraftFont.compileFontProvider(8, -197); + } + + private void initLocales() { + var locales = new HashMap(); + locales.put("container.inventory", " "); + + for (var lang: List.of("en_us", "ru_ru")) { + Resources.getResourcePack().language(Language.language(Key.key("minecraft", lang), locales)); + } + } + + private void initPlayerContextManager() { + MinecraftServer.getConnectionManager().setPlayerProvider(PlayerContextManager::create); + + MinecraftServer.getGlobalEventHandler().addListener(AsyncPlayerConfigurationEvent.class, event -> { + var player = (MsbPlayer) event.getPlayer(); + + log.info("Connected player '{}' (uuid: {}, address: {})", player.getUsername(), player.getUuid(), player.getPlayerConnection().getRemoteAddress()); + player.initContexts(); + }).addListener(PlayerSpawnEvent.class, event -> { + var player = (MsbPlayer) event.getPlayer(); + + if (event.isFirstSpawn()) player.emitSpawnSignalForContexts(); + }).addListener(PlayerDisconnectEvent.class, event -> { + var player = (MsbPlayer) event.getPlayer(); + + log.info("Disconnected player '{}' (uuid: {}, address: {})", player.getUsername(), player.getUuid(), player.getPlayerConnection().getRemoteAddress()); + player.disposeContexts(); + }); + + // Default contexts + PlayerContextManager.registerContext(KeyedBossBarContext.class, KeyedBossBarContext::new); + PlayerContextManager.registerContext(NavigatorContext.class, NavigatorContext::new); + PlayerContextManager.registerContext(TalksContext.class, TalksContext::new); + } + + private void initDefaultSkins() { + SkinStorage.loadSkin(SkinStorage.DEFAULT, ResourceFromJar.of("skins/default.msb3skin")); + } + + private void initDefaultDialogActionsAndConditions() { + DialogRegistry.registerActionHandler("close", new CloseDialogActionHandler()); + DialogRegistry.registerActionHandler("dialog", new DialogDialogActionHandler()); + + DialogRegistry.registerConditionHandler("always", new AlwaysDialogConditionHandler()); + DialogRegistry.registerConditionHandler("never", new NeverDialogConditionHandler()); + } +} diff --git a/api/src/main/java/ru/dragonestia/msb3/api/boot/ServerInitializer.java b/api/src/main/java/ru/dragonestia/msb3/api/boot/ServerInitializer.java new file mode 100644 index 0000000..1aaaf5a --- /dev/null +++ b/api/src/main/java/ru/dragonestia/msb3/api/boot/ServerInitializer.java @@ -0,0 +1,42 @@ +package ru.dragonestia.msb3.api.boot; + +import team.unnamed.creative.ResourcePack; + +/** + * Класс инициализатора сервера. Здесь имеются все точки состояний сервера. + * Создан для того чтобы было проще прописывать логику инициализации сервера + */ +public abstract class ServerInitializer { + + /** + * Запускается при самом старте приложения, когда никакая конфигурация сервера еще не подгружена + */ + public void onLoad() {} + + /** + * Запускается после инициализации всех стандартных модулей сервера + */ + public abstract void onDefaultModulesLoaded(); + + /** + * Запускается на стадии подготовки всех ресурсов, которые необходимо скомпилировать и добавить в ресурс-пак. + * + *

Именно на данном этапе необходимо регистрировать глифы. + * @param resourcePack Ресурс-пак + */ + public void onInitializeResources(ResourcePack resourcePack) {} + + /** + * Запускается после того как все пакеты ресурсов были успешно скомпилированы и готовы к последующей упаковке + * и отправке клиенту игрока. + * + *

Сервер в данном состоянии еще не полностью загружен + * @param resourcePack Скомпилированный ресурс-пак + */ + public void onResourcePackCompiled(ResourcePack resourcePack) {} + + /** + * Сервер полностью запущен и доступен для игры + */ + public void onServerStarted() {} +} diff --git a/api/src/main/java/ru/dragonestia/msb3/api/command/DebugCommands.java b/api/src/main/java/ru/dragonestia/msb3/api/command/DebugCommands.java new file mode 100644 index 0000000..414fac5 --- /dev/null +++ b/api/src/main/java/ru/dragonestia/msb3/api/command/DebugCommands.java @@ -0,0 +1,106 @@ +package ru.dragonestia.msb3.api.command; + +import lombok.experimental.UtilityClass; +import lombok.extern.log4j.Log4j2; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.event.ClickEvent; +import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.format.TextDecoration; +import net.minestom.server.MinecraftServer; +import net.minestom.server.command.builder.Command; +import net.minestom.server.command.builder.arguments.ArgumentEnum; +import net.minestom.server.command.builder.arguments.ArgumentType; +import net.minestom.server.entity.GameMode; +import net.minestom.server.entity.Player; +import net.minestom.server.utils.location.RelativeVec; +import ru.dragonestia.msb3.api.util.Env; + +import java.text.DecimalFormat; +import java.util.Locale; +import java.util.concurrent.atomic.AtomicInteger; + +@Log4j2 +@UtilityClass +public class DebugCommands { + + public void init() { + if (!Env.bool("MSB3_DEBUG").orElse(false)) return; + + registerPosCommand(); + registerTeleportCommand(); + registerGameModeCommand(); + + log.info("Registered debug commands"); + } + + private void registerPosCommand() { + var command = new Command("pos"); + var counter = new AtomicInteger(0); + command.setDefaultExecutor((sender, ctx) -> { + var player = (Player) sender; + var pos = player.getPosition(); + var decimal = new DecimalFormat("#.00"); + var result = "new Pos(%s, %s, %s, %sf, %sf)".formatted( + decimal.format(pos.x()).replace(",", "."), + decimal.format(pos.y()).replace(",", "."), + decimal.format(pos.z()).replace(",", "."), + decimal.format(pos.yaw()).replace(",", "."), + decimal.format(pos.pitch()).replace(",", ".") + ); + + var color = (counter.getAndIncrement() & 1) == 1 ? NamedTextColor.YELLOW : NamedTextColor.GOLD; + var component = Component.text(result, color, TextDecoration.UNDERLINED) + .clickEvent(ClickEvent.clickEvent(ClickEvent.Action.COPY_TO_CLIPBOARD, result)); + player.sendMessage(Component.text(counter.get() + ") Pos: ").append(component)); + + log.info("Pos: {}", result); + }); + MinecraftServer.getCommandManager().register(command); + } + + private void registerTeleportCommand() { + var command = new Command("tp"); + command.setDefaultExecutor((source, context) -> source.sendMessage(Component.text("Использование: /tp x y z"))); + var posArg = ArgumentType.RelativeBlockPosition("pos"); + command.addSyntax((sender, ctx) -> { + var player = (Player) sender; + RelativeVec relativeVec = ctx.get("pos"); + var position = player.getPosition().withCoord(relativeVec.fromSender(player)); + player.teleport(position); + player.sendMessage(Component.text("Выбили телепортирован на " + position)); + }, posArg); + MinecraftServer.getCommandManager().register(command); + } + + private void registerGameModeCommand() { + var command = new Command("gm", "gamemode"); + + ArgumentEnum gamemode = ArgumentType.Enum("gamemode", GameMode.class).setFormat(ArgumentEnum.Format.LOWER_CASED); + gamemode.setCallback((sender, exception) -> { + sender.sendMessage(Component.text("Invalid gamemode ", NamedTextColor.RED) + .append(Component.text(exception.getInput(), NamedTextColor.WHITE)) + .append(Component.text("!"))); + }); + + + command.setDefaultExecutor((sender, context) -> { + var commandName = context.getCommandName(); + sender.sendMessage(Component.text("Использование: /" + commandName + " ", NamedTextColor.RED)); + }); + + command.addSyntax((sender, context) -> { + if (!(sender instanceof Player p)) { + sender.sendMessage(Component.text("Please run this command in-game.", NamedTextColor.RED)); + return; + } + + var mode = context.get(gamemode); + + p.setGameMode(mode); + String gamemodeString = "gameMode." + mode.name().toLowerCase(Locale.ROOT); + Component gamemodeComponent = Component.translatable(gamemodeString); + sender.sendMessage(Component.translatable("commands.gamemode.success.self", gamemodeComponent)); + }, gamemode); + MinecraftServer.getCommandManager().register(command); + } +} diff --git a/api/src/main/java/ru/dragonestia/msb3/api/dialog/Dialog.java b/api/src/main/java/ru/dragonestia/msb3/api/dialog/Dialog.java new file mode 100644 index 0000000..18da16e --- /dev/null +++ b/api/src/main/java/ru/dragonestia/msb3/api/dialog/Dialog.java @@ -0,0 +1,77 @@ +package ru.dragonestia.msb3.api.dialog; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.extern.log4j.Log4j2; +import net.kyori.adventure.key.Key; +import net.minestom.server.entity.Player; +import ru.dragonestia.msb3.api.dialog.interlocutor.Interlocutor; +import ru.dragonestia.msb3.api.player.PlayerContext; +import ru.dragonestia.msb3.api.player.defaults.TalksContext; +import ru.dragonestia.msb3.api.resource.dialog.ButtonNumber; +import ru.dragonestia.msb3.api.ui.TalksThemes; +import ru.dragonestia.msb3.api.ui.dialogue.DialogueRenderer; +import ru.dragonestia.msb3.api.ui.dialogue.DialogueTheme; +import ru.dragonestia.msb3.api.util.DebugMessage; + +import java.util.ArrayList; +import java.util.List; + +@Log4j2 +@Getter +@Setter +@AllArgsConstructor +@NoArgsConstructor +public class Dialog { + + private Key id; + private String text; + private boolean rememberId; + private String themeId; + private List buttons; + + public void switchDialog(Player player, DialogueRenderer renderer, Interlocutor interlocutor) { + var ctx = PlayerContext.of(player, TalksContext.class); + if (rememberId) { + ctx.getOpenedDialogues().add(id.asString()); + DebugMessage.send(player, "Идентификатор диалога %s был сохранен в список просмотренных диалогов".formatted(id.asString())); + } + + DialogueTheme theme = DialogueTheme.builder().build(); + if (themeId != null) { + var themeOpt = TalksThemes.getDialogueTheme(themeId); + if (themeOpt.isPresent()) { + theme = themeOpt.get(); + } else { + DebugMessage.sendError(player, "Не найдена тема для диалога с идентификатором %s".formatted(themeId)); + log.error("Unknown theme: {}", themeId); + } + } + renderer.setTheme(theme.toBuilder() + .setAvatar(interlocutor.getDialogAvatar()) + .build()); + + renderer.setText(text); + + for (var buttonNumber: ButtonNumber.values()) renderer.removeButton(buttonNumber); + var buttons = new ArrayList<>(getButtons()); + buttons.removeIf(button -> !button.checkConditions(player, this, button, renderer)); + for (var buttonNumber: ButtonNumber.values()) { + if (buttons.isEmpty()) break; + var button = buttons.removeFirst(); + renderer.setButton(buttonNumber, button.getText(), buttonCtx -> { + var click = new DialogButtonClick(buttonCtx.player(), interlocutor, this, button, buttonCtx.buttonNumber(), buttonCtx.renderer()); + button.onClick(click); + }); + } + renderer.rerender(); + } + + public synchronized void open(Player player, Interlocutor interlocutor) { + var renderer = DialogueRenderer.getRenderer(player).orElseGet(() -> new DialogueRenderer(player, DialogueTheme.builder().build())); + switchDialog(player, renderer, interlocutor); + renderer.show(); + } +} diff --git a/api/src/main/java/ru/dragonestia/msb3/api/dialog/DialogButton.java b/api/src/main/java/ru/dragonestia/msb3/api/dialog/DialogButton.java new file mode 100644 index 0000000..d995f95 --- /dev/null +++ b/api/src/main/java/ru/dragonestia/msb3/api/dialog/DialogButton.java @@ -0,0 +1,93 @@ +package ru.dragonestia.msb3.api.dialog; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.extern.log4j.Log4j2; +import net.kyori.adventure.key.Key; +import net.minestom.server.entity.Player; +import ru.dragonestia.msb3.api.player.PlayerContext; +import ru.dragonestia.msb3.api.player.defaults.TalksContext; +import ru.dragonestia.msb3.api.ui.dialogue.DialogueRenderer; +import ru.dragonestia.msb3.api.util.DebugMessage; + +import java.util.List; +import java.util.Map; +import java.util.UUID; + +@Log4j2 +@Getter +@Setter +@AllArgsConstructor +@NoArgsConstructor +public class DialogButton { + + private final UUID uuid = UUID.randomUUID(); + private Key id; + private String text; + private String actionId; + private Map params; + private List conditions; + + public boolean checkConditions(Player player, Dialog dialog, DialogButton button, DialogueRenderer renderer) { + if (PlayerContext.of(player, TalksContext.class).isIgnoreDialogConditions()) { + return true; + } + + for (var condition: conditions) { + if (!condition.check(player, dialog, button, renderer)) return false; + } + return true; + } + + public void onClick(DialogButtonClick click) { + var player = click.player(); + + if (actionId == null) { + DebugMessage.sendError(player, "actionId для кнопки имеет значение null"); + return; + } + + var action = DialogRegistry.findActionHandler(actionId); + if (action.isEmpty()) { + DebugMessage.sendError(player, "Действие кнопки с идентификатором '%s' не зарегистрировано".formatted(actionId)); + return; + } + + boolean success; + try { + DebugMessage.send(player, "Выполнение действия диалога actionId=%s params=%s".formatted(actionId, params)); + action.get().handle(click, params); + + success = true; + } catch (Exception ex) { + log.error(ex.getMessage(), ex); + DebugMessage.sendError(player, "Произошла ошибка при выполнении действия клика кнопки. actionId=%s params=%s\n%s: %s".formatted(action, params, ex.getClass().getSimpleName(), ex.getMessage())); + success = false; + } + + if (!success) return; + + if (id != null) { + var ctx = PlayerContext.of(player, TalksContext.class); + ctx.getClickedButtons().add(id.asString()); + DebugMessage.send(player, "Идентификатор кнопки диалога %s был сохранен в список нажатых кнопок".formatted(id.asString())); + } + } + + @Override + public boolean equals(Object obj) { + if (obj == null) return false; + if (obj == this) return true; + if (obj instanceof DialogButton button) { + return uuid.equals(button.uuid); + } + return false; + } + + @Override + public int hashCode() { + return uuid.hashCode(); + } +} diff --git a/api/src/main/java/ru/dragonestia/msb3/api/dialog/DialogButtonClick.java b/api/src/main/java/ru/dragonestia/msb3/api/dialog/DialogButtonClick.java new file mode 100644 index 0000000..2d8df81 --- /dev/null +++ b/api/src/main/java/ru/dragonestia/msb3/api/dialog/DialogButtonClick.java @@ -0,0 +1,15 @@ +package ru.dragonestia.msb3.api.dialog; + +import net.minestom.server.entity.Player; +import ru.dragonestia.msb3.api.dialog.interlocutor.Interlocutor; +import ru.dragonestia.msb3.api.resource.dialog.ButtonNumber; +import ru.dragonestia.msb3.api.ui.dialogue.DialogueRenderer; + +public record DialogButtonClick( + Player player, + Interlocutor interlocutor, + Dialog dialog, + DialogButton dialogButton, + ButtonNumber buttonNumber, + DialogueRenderer renderer +) {} diff --git a/api/src/main/java/ru/dragonestia/msb3/api/dialog/DialogCondition.java b/api/src/main/java/ru/dragonestia/msb3/api/dialog/DialogCondition.java new file mode 100644 index 0000000..026c9f1 --- /dev/null +++ b/api/src/main/java/ru/dragonestia/msb3/api/dialog/DialogCondition.java @@ -0,0 +1,43 @@ +package ru.dragonestia.msb3.api.dialog; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.extern.log4j.Log4j2; +import net.minestom.server.entity.Player; +import ru.dragonestia.msb3.api.ui.dialogue.DialogueRenderer; +import ru.dragonestia.msb3.api.util.DebugMessage; + +import java.util.Map; + +@Log4j2 +@Getter +@Setter +@AllArgsConstructor +@NoArgsConstructor +public class DialogCondition { + + private String conditionId; + private Map params; + + public boolean check(Player player, Dialog dialog, DialogButton button, DialogueRenderer renderer) { + if (conditionId == null) { + DebugMessage.sendError(player, "Идентификатор условия для кнопки имеет значение null"); + return false; + } + + var condition = DialogRegistry.findConditionHandler(conditionId); + if (condition.isEmpty()) { + return false; + } + + try { + return condition.get().check(player, dialog, button, renderer, params); + } catch (Exception ex) { + log.error(ex.getMessage(), ex); + DebugMessage.send(player, "Во время проверки условия для кнопки произошла ошибка conditionId=%s params=%s".formatted(conditionId, params)); + return false; + } + } +} diff --git a/api/src/main/java/ru/dragonestia/msb3/api/dialog/DialogRegistry.java b/api/src/main/java/ru/dragonestia/msb3/api/dialog/DialogRegistry.java new file mode 100644 index 0000000..c1ae0d2 --- /dev/null +++ b/api/src/main/java/ru/dragonestia/msb3/api/dialog/DialogRegistry.java @@ -0,0 +1,60 @@ +package ru.dragonestia.msb3.api.dialog; + +import lombok.Getter; +import lombok.Setter; +import lombok.experimental.UtilityClass; +import lombok.extern.log4j.Log4j2; +import net.kyori.adventure.key.Key; +import ru.dragonestia.msb3.api.dialog.action.DialogActionHandler; +import ru.dragonestia.msb3.api.dialog.condition.DialogConditionHandler; +import ru.dragonestia.msb3.api.dialog.data.VoidPlayerDataProvider; +import ru.dragonestia.msb3.api.dialog.data.PlayerDataProvider; + +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; + +@Log4j2 +@UtilityClass +public class DialogRegistry { + + private final Map dialogs = new ConcurrentHashMap<>(); + private final Map actionHandlers = new HashMap<>(); + private final Map conditionHandlers = new HashMap<>(); + @Getter @Setter private PlayerDataProvider playerDataProvider = new VoidPlayerDataProvider(); + + public void storeDialog(Dialog dialog) { + if (dialog.getId() == null) { + throw new NullPointerException("Dialog id is null"); + } + + dialogs.put(dialog.getId(), dialog); + } + + public Optional

findDialog(Key key) { + return Optional.ofNullable(dialogs.get(key)); + } + + public void registerActionHandler(String id, DialogActionHandler handler) { + var prev = actionHandlers.put(id, handler); + if (prev != null) { + log.warn("Duplicate action handler for id '{}'. Removing prev", id); + } + } + + public Optional findActionHandler(String id) { + return Optional.ofNullable(actionHandlers.get(id)); + } + + public void registerConditionHandler(String id, DialogConditionHandler handler) { + var prev = conditionHandlers.put(id, handler); + if (prev != null) { + log.warn("Duplicate condition handler for id '{}'. Removing prev", id); + } + } + + public Optional findConditionHandler(String id) { + return Optional.ofNullable(conditionHandlers.get(id)); + } +} diff --git a/api/src/main/java/ru/dragonestia/msb3/api/dialog/action/CloseDialogActionHandler.java b/api/src/main/java/ru/dragonestia/msb3/api/dialog/action/CloseDialogActionHandler.java new file mode 100644 index 0000000..d1995ba --- /dev/null +++ b/api/src/main/java/ru/dragonestia/msb3/api/dialog/action/CloseDialogActionHandler.java @@ -0,0 +1,13 @@ +package ru.dragonestia.msb3.api.dialog.action; + +import ru.dragonestia.msb3.api.dialog.DialogButtonClick; + +import java.util.Map; + +public class CloseDialogActionHandler implements DialogActionHandler { + + @Override + public void handle(DialogButtonClick click, Map params) { + click.renderer().close(false); + } +} diff --git a/api/src/main/java/ru/dragonestia/msb3/api/dialog/action/DialogActionHandler.java b/api/src/main/java/ru/dragonestia/msb3/api/dialog/action/DialogActionHandler.java new file mode 100644 index 0000000..6d3eda0 --- /dev/null +++ b/api/src/main/java/ru/dragonestia/msb3/api/dialog/action/DialogActionHandler.java @@ -0,0 +1,10 @@ +package ru.dragonestia.msb3.api.dialog.action; + +import ru.dragonestia.msb3.api.dialog.DialogButtonClick; + +import java.util.Map; + +public interface DialogActionHandler { + + void handle(DialogButtonClick click, Map params); +} diff --git a/api/src/main/java/ru/dragonestia/msb3/api/dialog/action/DialogDialogActionHandler.java b/api/src/main/java/ru/dragonestia/msb3/api/dialog/action/DialogDialogActionHandler.java new file mode 100644 index 0000000..780fe74 --- /dev/null +++ b/api/src/main/java/ru/dragonestia/msb3/api/dialog/action/DialogDialogActionHandler.java @@ -0,0 +1,32 @@ +package ru.dragonestia.msb3.api.dialog.action; + +import lombok.extern.log4j.Log4j2; +import net.kyori.adventure.key.Key; +import ru.dragonestia.msb3.api.dialog.DialogButtonClick; +import ru.dragonestia.msb3.api.dialog.DialogRegistry; +import ru.dragonestia.msb3.api.util.DebugMessage; + +import java.util.Map; + +@Log4j2 +public class DialogDialogActionHandler implements DialogActionHandler { + + @Override + public void handle(DialogButtonClick click, Map params) { + var player = click.player(); + var dialogId = params.get("dialogId"); + if (dialogId == null) { + DebugMessage.sendError(player, "Отсутствует обязательный параметр dialogId для команды dialog"); + return; + } + + var dialog = DialogRegistry.findDialog(Key.key(dialogId)); + if (dialog.isEmpty()) { + log.error("Dialog {} not found", dialogId); + DebugMessage.sendError(player, "Диалог с идентификатором %s не найден".formatted(dialogId)); + return; + } + + dialog.get().switchDialog(player, click.renderer(), click.interlocutor()); + } +} diff --git a/api/src/main/java/ru/dragonestia/msb3/api/dialog/condition/AlwaysDialogConditionHandler.java b/api/src/main/java/ru/dragonestia/msb3/api/dialog/condition/AlwaysDialogConditionHandler.java new file mode 100644 index 0000000..be28e40 --- /dev/null +++ b/api/src/main/java/ru/dragonestia/msb3/api/dialog/condition/AlwaysDialogConditionHandler.java @@ -0,0 +1,16 @@ +package ru.dragonestia.msb3.api.dialog.condition; + +import net.minestom.server.entity.Player; +import ru.dragonestia.msb3.api.dialog.Dialog; +import ru.dragonestia.msb3.api.dialog.DialogButton; +import ru.dragonestia.msb3.api.ui.dialogue.DialogueRenderer; + +import java.util.Map; + +public class AlwaysDialogConditionHandler implements DialogConditionHandler { + + @Override + public boolean check(Player player, Dialog dialog, DialogButton button, DialogueRenderer renderer, Map params) { + return true; + } +} diff --git a/api/src/main/java/ru/dragonestia/msb3/api/dialog/condition/DialogConditionHandler.java b/api/src/main/java/ru/dragonestia/msb3/api/dialog/condition/DialogConditionHandler.java new file mode 100644 index 0000000..107ab48 --- /dev/null +++ b/api/src/main/java/ru/dragonestia/msb3/api/dialog/condition/DialogConditionHandler.java @@ -0,0 +1,13 @@ +package ru.dragonestia.msb3.api.dialog.condition; + +import net.minestom.server.entity.Player; +import ru.dragonestia.msb3.api.dialog.Dialog; +import ru.dragonestia.msb3.api.dialog.DialogButton; +import ru.dragonestia.msb3.api.ui.dialogue.DialogueRenderer; + +import java.util.Map; + +public interface DialogConditionHandler { + + boolean check(Player player, Dialog dialog, DialogButton button, DialogueRenderer renderer, Map params); +} diff --git a/api/src/main/java/ru/dragonestia/msb3/api/dialog/condition/NeverDialogConditionHandler.java b/api/src/main/java/ru/dragonestia/msb3/api/dialog/condition/NeverDialogConditionHandler.java new file mode 100644 index 0000000..edb9d5f --- /dev/null +++ b/api/src/main/java/ru/dragonestia/msb3/api/dialog/condition/NeverDialogConditionHandler.java @@ -0,0 +1,16 @@ +package ru.dragonestia.msb3.api.dialog.condition; + +import net.minestom.server.entity.Player; +import ru.dragonestia.msb3.api.dialog.Dialog; +import ru.dragonestia.msb3.api.dialog.DialogButton; +import ru.dragonestia.msb3.api.ui.dialogue.DialogueRenderer; + +import java.util.Map; + +public class NeverDialogConditionHandler implements DialogConditionHandler { + + @Override + public boolean check(Player player, Dialog dialog, DialogButton button, DialogueRenderer renderer, Map params) { + return false; + } +} diff --git a/api/src/main/java/ru/dragonestia/msb3/api/dialog/data/PlayerDataProvider.java b/api/src/main/java/ru/dragonestia/msb3/api/dialog/data/PlayerDataProvider.java new file mode 100644 index 0000000..3a8b653 --- /dev/null +++ b/api/src/main/java/ru/dragonestia/msb3/api/dialog/data/PlayerDataProvider.java @@ -0,0 +1,10 @@ +package ru.dragonestia.msb3.api.dialog.data; + +import ru.dragonestia.msb3.api.player.MsbPlayer; + +public interface PlayerDataProvider { + + TalksPlayerData load(MsbPlayer player); + + void save(MsbPlayer player, TalksPlayerData data); +} diff --git a/api/src/main/java/ru/dragonestia/msb3/api/dialog/data/TalksPlayerData.java b/api/src/main/java/ru/dragonestia/msb3/api/dialog/data/TalksPlayerData.java new file mode 100644 index 0000000..4253fe8 --- /dev/null +++ b/api/src/main/java/ru/dragonestia/msb3/api/dialog/data/TalksPlayerData.java @@ -0,0 +1,8 @@ +package ru.dragonestia.msb3.api.dialog.data; + +import java.util.Set; + +public record TalksPlayerData( + Set openedDialogs, + Set clickedButtons +) {} \ No newline at end of file diff --git a/api/src/main/java/ru/dragonestia/msb3/api/dialog/data/VoidPlayerDataProvider.java b/api/src/main/java/ru/dragonestia/msb3/api/dialog/data/VoidPlayerDataProvider.java new file mode 100644 index 0000000..6dcd247 --- /dev/null +++ b/api/src/main/java/ru/dragonestia/msb3/api/dialog/data/VoidPlayerDataProvider.java @@ -0,0 +1,19 @@ +package ru.dragonestia.msb3.api.dialog.data; + +import ru.dragonestia.msb3.api.player.MsbPlayer; + +import java.util.HashSet; + +public class VoidPlayerDataProvider implements PlayerDataProvider { + + @Override + public TalksPlayerData load(MsbPlayer player) { + return new TalksPlayerData( + new HashSet<>(), + new HashSet<>() + ); + } + + @Override + public void save(MsbPlayer player, TalksPlayerData data) {} +} diff --git a/api/src/main/java/ru/dragonestia/msb3/api/dialog/interlocutor/AnonymousInterlocutor.java b/api/src/main/java/ru/dragonestia/msb3/api/dialog/interlocutor/AnonymousInterlocutor.java new file mode 100644 index 0000000..b0ad076 --- /dev/null +++ b/api/src/main/java/ru/dragonestia/msb3/api/dialog/interlocutor/AnonymousInterlocutor.java @@ -0,0 +1,15 @@ +package ru.dragonestia.msb3.api.dialog.interlocutor; + +import ru.dragonestia.msb3.api.resource.DialogueResources; +import ru.dragonestia.msb3.api.resource.MonologueResources; + +public class AnonymousInterlocutor extends SimpleInterlocutor { + + public AnonymousInterlocutor(String name) { + super( + name, + DialogueResources.getAvatar(DialogueResources.DEFAULT).orElseThrow(), + MonologueResources.getAvatar(MonologueResources.DEFAULT).orElseThrow() + ); + } +} diff --git a/api/src/main/java/ru/dragonestia/msb3/api/dialog/interlocutor/Interlocutor.java b/api/src/main/java/ru/dragonestia/msb3/api/dialog/interlocutor/Interlocutor.java new file mode 100644 index 0000000..28445d4 --- /dev/null +++ b/api/src/main/java/ru/dragonestia/msb3/api/dialog/interlocutor/Interlocutor.java @@ -0,0 +1,43 @@ +package ru.dragonestia.msb3.api.dialog.interlocutor; + +import net.minestom.server.entity.Player; +import ru.dragonestia.msb3.api.dialog.Dialog; +import ru.dragonestia.msb3.api.ui.dialogue.DialogueRenderer; +import ru.dragonestia.msb3.api.ui.monologue.Monologue; +import ru.dragonestia.msb3.api.ui.monologue.MonologueTheme; +import ru.dragonestia.msb3.resource.glyph.GlyphImage; + +public interface Interlocutor { + + String getInterlocutorName(); + + GlyphImage getDialogAvatar(); + + GlyphImage getMonologAvatar(); + + default MonologueTheme getMonologueTheme() { + return MonologueTheme.builder().build(); + } + + default void sendMonolog(Player receiver, String message) { + sendMonolog(receiver, message, getMonologueTheme()); + } + + default void sendMonolog(Player receiver, String message, MonologueTheme theme) { + Monologue.create(receiver, getInterlocutorName(), message).show(theme); + } + + default void sendDialog(Player receiver, Dialog dialog) { + sendDialog(receiver, dialog, false); + } + + default void sendDialog(Player receiver, Dialog dialog, boolean inDialogueWindowNow) { + if (inDialogueWindowNow) { + var renderer = DialogueRenderer.getRenderer(receiver).orElseThrow(); + dialog.switchDialog(receiver, renderer, this); + renderer.rerender(); + } else { + dialog.open(receiver, this); + } + } +} diff --git a/api/src/main/java/ru/dragonestia/msb3/api/dialog/interlocutor/SimpleInterlocutor.java b/api/src/main/java/ru/dragonestia/msb3/api/dialog/interlocutor/SimpleInterlocutor.java new file mode 100644 index 0000000..62d0f93 --- /dev/null +++ b/api/src/main/java/ru/dragonestia/msb3/api/dialog/interlocutor/SimpleInterlocutor.java @@ -0,0 +1,29 @@ +package ru.dragonestia.msb3.api.dialog.interlocutor; + +import lombok.AllArgsConstructor; +import lombok.Setter; +import ru.dragonestia.msb3.resource.glyph.GlyphImage; + +@Setter +@AllArgsConstructor +public class SimpleInterlocutor implements Interlocutor { + + private String name; + private GlyphImage dialogAvatar; + private GlyphImage monologAvatar; + + @Override + public String getInterlocutorName() { + return name; + } + + @Override + public GlyphImage getDialogAvatar() { + return dialogAvatar; + } + + @Override + public GlyphImage getMonologAvatar() { + return monologAvatar; + } +} diff --git a/api/src/main/java/ru/dragonestia/msb3/api/dialog/provider/DialogFileProvider.java b/api/src/main/java/ru/dragonestia/msb3/api/dialog/provider/DialogFileProvider.java new file mode 100644 index 0000000..d1346b8 --- /dev/null +++ b/api/src/main/java/ru/dragonestia/msb3/api/dialog/provider/DialogFileProvider.java @@ -0,0 +1,134 @@ +package ru.dragonestia.msb3.api.dialog.provider; + +import lombok.Getter; +import lombok.Setter; +import lombok.SneakyThrows; +import lombok.experimental.UtilityClass; +import lombok.extern.log4j.Log4j2; +import net.kyori.adventure.key.Key; +import org.spongepowered.configurate.gson.GsonConfigurationLoader; +import org.spongepowered.configurate.hocon.HoconConfigurationLoader; +import org.spongepowered.configurate.objectmapping.ConfigSerializable; +import org.spongepowered.configurate.yaml.YamlConfigurationLoader; +import ru.dragonestia.msb3.api.dialog.Dialog; +import ru.dragonestia.msb3.api.dialog.DialogButton; +import ru.dragonestia.msb3.api.dialog.DialogCondition; +import ru.dragonestia.msb3.api.dialog.DialogRegistry; +import team.unnamed.creative.base.Writable; + +import java.util.*; + +@Log4j2 +@UtilityClass +public class DialogFileProvider { + + @SneakyThrows + public void readFromHocon(Writable writable) { + var root = HoconConfigurationLoader.builder() + .buildAndLoadString(writable.toUTF8String()); + loadDialogs(Objects.requireNonNull(root.get(DialogsList.class))); + } + + @SneakyThrows + public void readFromJson(Writable writable) { + var root = GsonConfigurationLoader.builder() + .buildAndLoadString(writable.toUTF8String()); + loadDialogs(Objects.requireNonNull(root.get(DialogsList.class))); + } + + @SneakyThrows + public void readFromYaml(Writable writable) { + var root = YamlConfigurationLoader.builder() + .buildAndLoadString(writable.toUTF8String()); + loadDialogs(Objects.requireNonNull(root.get(DialogsList.class))); + } + + private void loadDialogs(DialogsList source) { + for (var entry: source.dialogs.entrySet()) { + var dialogId = entry.getKey(); + var dialogEntry = entry.getValue(); + var dialog = new Dialog(); + + dialog.setId(Key.key(dialogId)); + dialog.setText(Objects.requireNonNull(dialogEntry.text, "Text is null inside dialog " + dialogId)); + dialog.setRememberId(dialogEntry.remember); + dialog.setThemeId(dialogEntry.theme); + + var buttons = new ArrayList(); + for (var buttonEntry: dialogEntry.buttons) { + var button = new DialogButton(); + + if (buttonEntry.id != null) button.setId(Key.key(buttonEntry.id)); + button.setText(Objects.requireNonNull(buttonEntry.text)); + button.setActionId(Objects.requireNonNull(buttonEntry.action)); + button.setParams(Objects.requireNonNull(buttonEntry.params)); + + var conditions = new ArrayList(); + for (var conditionEntry: buttonEntry.conditions) { + var condition = new DialogCondition(); + + condition.setConditionId(Objects.requireNonNull(conditionEntry.id)); + condition.setParams(Objects.requireNonNull(conditionEntry.params)); + } + button.setConditions(conditions); + + buttons.add(button); + } + dialog.setButtons(buttons); + + DialogRegistry.storeDialog(dialog); + } + } + + @Getter + @Setter + @ConfigSerializable + public static class DialogsList { + + private Map dialogs; + } + + @Setter + @Getter + @ConfigSerializable + public static class DialogEntry { + private String text; + private boolean remember = false; + private String theme = null; + private List buttons; + + @Override + public String toString() { + return "{text=" + text + ", remember=" + remember + ", theme=" + theme + ", buttons=" + buttons + "}"; + } + } + + @Setter + @Getter + @ConfigSerializable + public static class ButtonEntry { + private String id = null; + private String text; + private String action; + private Map params = new HashMap<>(); + private List conditions = new ArrayList<>(); + + @Override + public String toString() { + return "{id=" + id + ", text=" + text + ", action=" + action + ", params=" + params + ", conditions=" + conditions + "}"; + } + } + + @Setter + @Getter + @ConfigSerializable + public static class ConditionEntry { + private String id; + private Map params = new HashMap<>(); + + @Override + public String toString() { + return "{id=" + id + ", params=" + params + "}"; + } + } +} diff --git a/api/src/main/java/ru/dragonestia/msb3/api/entity/Human.java b/api/src/main/java/ru/dragonestia/msb3/api/entity/Human.java new file mode 100644 index 0000000..c0e9286 --- /dev/null +++ b/api/src/main/java/ru/dragonestia/msb3/api/entity/Human.java @@ -0,0 +1,153 @@ +package ru.dragonestia.msb3.api.entity; + +import lombok.Getter; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.format.TextColor; +import net.minestom.server.MinecraftServer; +import net.minestom.server.coordinate.Point; +import net.minestom.server.coordinate.Pos; +import net.minestom.server.coordinate.Vec; +import net.minestom.server.entity.*; +import net.minestom.server.entity.attribute.Attribute; +import net.minestom.server.entity.metadata.display.AbstractDisplayMeta; +import net.minestom.server.entity.metadata.display.TextDisplayMeta; +import net.minestom.server.instance.Instance; +import net.minestom.server.network.packet.server.play.PlayerInfoRemovePacket; +import net.minestom.server.network.packet.server.play.PlayerInfoUpdatePacket; +import net.minestom.server.network.packet.server.play.TeamsPacket; +import net.minestom.server.scoreboard.Team; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Predicate; + +public class Human extends EntityCreature { + + private static final AtomicInteger freeId = new AtomicInteger(0); + private static final Map npcTeams = new ConcurrentHashMap<>(); + private static final Point displayNamePositionOffset = new Vec(0, 2, 0); + + @Getter private Component name; + private final String username; + @Getter private final PlayerSkin skin; + private final NamedTextColor teamColor; + private final PlayerInfoUpdatePacket createPacket; + private final PlayerInfoRemovePacket removePacket; + private final Team team; + private Entity displayNameEntity; + private Predicate lastViewableRule; + + public Human(String name, PlayerSkin skin) { + this(Component.text(name, NamedTextColor.YELLOW), skin); + } + + public Human(Component name, PlayerSkin skin) { + this(name, skin, NamedTextColor.YELLOW); + } + + public Human(Component name, PlayerSkin skin, NamedTextColor teamColor) { + super(EntityType.PLAYER); + this.name = name; + this.username = "Human_" + freeId.incrementAndGet(); + this.skin = skin; + this.teamColor = teamColor; + this.createPacket = getCreatePacket(); + this.removePacket = new PlayerInfoRemovePacket(getUuid()); + this.team = pickTeam(); + + getEntityMeta().setCustomNameVisible(false); + setBoundingBox(0.6, 1.8, 0.6); + getAttribute(Attribute.MOVEMENT_SPEED).setBaseValue(0.2F); + } + + private PlayerInfoUpdatePacket getCreatePacket() { + var properties = List.of(new PlayerInfoUpdatePacket.Property("textures", skin.textures(), skin.signature())); + return new PlayerInfoUpdatePacket(PlayerInfoUpdatePacket.Action.ADD_PLAYER, + new PlayerInfoUpdatePacket.Entry(getUuid(), username, properties, false, 0, GameMode.CREATIVE, name, null, 0)); + } + + @Override + public CompletableFuture setInstance(@NotNull Instance instance, @NotNull Pos spawnPosition) { + return super.setInstance(instance, spawnPosition) + .thenRun(this::getDisplayNameEntity); + } + + @Override + public @NotNull CompletableFuture teleport(@NotNull Pos position, @NotNull Vec velocity, long @Nullable [] chunks, int flags, boolean shouldConfirm) { + return super.teleport(position, velocity, chunks, flags, shouldConfirm) + .thenRun(() -> getDisplayNameEntity().teleport(position.add(displayNamePositionOffset), chunks, flags, shouldConfirm)); + } + + @Override + public void refreshPosition(@NotNull Pos newPosition, boolean ignoreView, boolean sendPackets) { + super.refreshPosition(newPosition, ignoreView, sendPackets); + getDisplayNameEntity().refreshPosition(newPosition.add(displayNamePositionOffset), ignoreView, sendPackets); + } + + @Override + protected void remove(boolean permanent) { + super.remove(permanent); + if (displayNameEntity != null) displayNameEntity.remove(); + } + + @Override + public void updateNewViewer(@NotNull Player player) { + player.sendPacket(createPacket); + super.updateNewViewer(player); + player.sendPacket(new TeamsPacket(team.getTeamName(), new TeamsPacket.AddEntitiesToTeamAction(List.of(username)))); + } + + @Override + public void updateOldViewer(@NotNull Player player) { + player.sendPacket(removePacket); + super.updateOldViewer(player); + } + + @Override + public void updateViewableRule() { + super.updateViewableRule(); + if (displayNameEntity != null) displayNameEntity.updateViewableRule(); + } + + @Override + public void updateViewableRule(@Nullable Predicate predicate) { + super.updateViewableRule(lastViewableRule = predicate); + if (displayNameEntity != null) displayNameEntity.updateViewableRule(predicate); + } + + private Team pickTeam() { + return npcTeams.computeIfAbsent(teamColor.asHexString(), $ -> MinecraftServer.getTeamManager() + .createBuilder("NPC_r%s_g%s_b%s".formatted(teamColor.red(), teamColor.green(), teamColor.blue())) + .collisionRule(TeamsPacket.CollisionRule.ALWAYS) + .teamColor(teamColor) + .nameTagVisibility(TeamsPacket.NameTagVisibility.NEVER) + .build()); + } + + private synchronized Entity getDisplayNameEntity() { + if (displayNameEntity == null) { + displayNameEntity = new Entity(EntityType.TEXT_DISPLAY); + var meta = (TextDisplayMeta) displayNameEntity.getEntityMeta(); + meta.setText(name); + meta.setHasNoGravity(true); + meta.setBillboardRenderConstraints(AbstractDisplayMeta.BillboardConstraints.VERTICAL); + if (lastViewableRule != null) displayNameEntity.updateViewableRule(lastViewableRule); + displayNameEntity.setInstance(instance, getPosition().add(displayNamePositionOffset)); + } + return displayNameEntity; + } + + public void setName(Component name) { + this.name = name; + if (displayNameEntity != null) { + var meta = (TextDisplayMeta) displayNameEntity.getEntityMeta(); + meta.setText(name); + } + } +} diff --git a/api/src/main/java/ru/dragonestia/msb3/api/entity/NPC.java b/api/src/main/java/ru/dragonestia/msb3/api/entity/NPC.java new file mode 100644 index 0000000..39a4bc0 --- /dev/null +++ b/api/src/main/java/ru/dragonestia/msb3/api/entity/NPC.java @@ -0,0 +1,13 @@ +package ru.dragonestia.msb3.api.entity; + +import net.minestom.server.entity.Player; +import ru.dragonestia.msb3.api.event.NPCClickEvent; + +public interface NPC { + + default boolean supportedClickType(NPCClickEvent.ClickType clickType) { + return true; + } + + void onClickByPlayer(Player player, NPCClickEvent.ClickType clickType); +} diff --git a/api/src/main/java/ru/dragonestia/msb3/api/entity/PickableItem.java b/api/src/main/java/ru/dragonestia/msb3/api/entity/PickableItem.java new file mode 100644 index 0000000..18170fd --- /dev/null +++ b/api/src/main/java/ru/dragonestia/msb3/api/entity/PickableItem.java @@ -0,0 +1,36 @@ +package ru.dragonestia.msb3.api.entity; + +import net.minestom.server.MinecraftServer; +import net.minestom.server.entity.ItemEntity; +import net.minestom.server.entity.Player; +import net.minestom.server.event.item.PickupItemEvent; +import net.minestom.server.item.ItemStack; +import org.jetbrains.annotations.NotNull; + +import java.time.temporal.ChronoUnit; + +public class PickableItem extends ItemEntity { + + public PickableItem(@NotNull ItemStack itemStack) { + super(itemStack); + + setPickable(true); + setMergeable(false); + setPickupDelay(500, ChronoUnit.MILLIS); + } + + public static void registerEvent() { + MinecraftServer.getGlobalEventHandler().addListener(PickupItemEvent.class, event -> { + if (!(event.getItemEntity() instanceof PickableItem)) return; + if (!(event.getEntity() instanceof Player player)) return; + + var itemEntity = event.getItemEntity(); + var inv = player.getInventory(); + var item = itemEntity.getItemStack(); + + if (!inv.addItemStack(item)) { + event.setCancelled(true); + } + }); + } +} diff --git a/api/src/main/java/ru/dragonestia/msb3/api/entity/StaticEntity.java b/api/src/main/java/ru/dragonestia/msb3/api/entity/StaticEntity.java new file mode 100644 index 0000000..4dbdcc9 --- /dev/null +++ b/api/src/main/java/ru/dragonestia/msb3/api/entity/StaticEntity.java @@ -0,0 +1,19 @@ +package ru.dragonestia.msb3.api.entity; + +import net.minestom.server.coordinate.Pos; +import net.minestom.server.entity.Entity; +import net.minestom.server.entity.EntityType; +import org.jetbrains.annotations.NotNull; + +import java.util.function.Supplier; + +public class StaticEntity extends Entity { + + public StaticEntity(@NotNull EntityType entityType) { + super(entityType); + + setNoGravity(true); + } + + public record Spot(Pos pos, Supplier supplier) {} +} diff --git a/api/src/main/java/ru/dragonestia/msb3/api/entity/goal/LookCloseGoal.java b/api/src/main/java/ru/dragonestia/msb3/api/entity/goal/LookCloseGoal.java new file mode 100644 index 0000000..de6f120 --- /dev/null +++ b/api/src/main/java/ru/dragonestia/msb3/api/entity/goal/LookCloseGoal.java @@ -0,0 +1,83 @@ +package ru.dragonestia.msb3.api.entity.goal; + +import lombok.Getter; +import lombok.Setter; +import net.minestom.server.entity.EntityCreature; +import net.minestom.server.entity.Player; +import net.minestom.server.entity.ai.GoalSelector; +import org.jetbrains.annotations.NotNull; + +public class LookCloseGoal extends GoalSelector { + + private final static int MAX_DIST = 7 * 7; + + @Setter @Getter private volatile boolean enabled = false; + private boolean lastSeePlayer = false; + @Getter private float defaultYaw; + @Getter private float defaultPitch; + + public LookCloseGoal(@NotNull EntityCreature entityCreature) { + super(entityCreature); + + defaultYaw = entityCreature.getPosition().yaw(); + defaultPitch = entityCreature.getPosition().pitch(); + } + + @Override + public boolean shouldStart() { + return true; + } + + @Override + public void start() {} + + @Override + public void tick(long l) { + if ((l & 0b11) == 0) return; + + if (!enabled) { + if (lastSeePlayer) { + entityCreature.setView(defaultYaw, defaultPitch); + lastSeePlayer = false; + } + return; + } + + Player closestPlayer = null; + double closestDistance = Double.MAX_VALUE; + + for (var player: entityCreature.getViewers()) { + var distance = player.getPosition().distanceSquared(entityCreature.getPosition()); + if (distance > MAX_DIST) continue; + + if (distance < closestDistance) { + closestDistance = distance; + closestPlayer = player; + } + } + + if (closestPlayer != null) { + entityCreature.lookAt(closestPlayer); + lastSeePlayer = true; + return; + } + + if (lastSeePlayer) { + entityCreature.setView(defaultYaw, defaultPitch); + lastSeePlayer = false; + } + } + + @Override + public boolean shouldEnd() { + return false; + } + + @Override + public void end() {} + + public void setDefaultRotation(float yaw, float pitch) { + defaultYaw = yaw; + defaultPitch = pitch; + } +} diff --git a/api/src/main/java/ru/dragonestia/msb3/api/event/NPCClickEvent.java b/api/src/main/java/ru/dragonestia/msb3/api/event/NPCClickEvent.java new file mode 100644 index 0000000..24be8e4 --- /dev/null +++ b/api/src/main/java/ru/dragonestia/msb3/api/event/NPCClickEvent.java @@ -0,0 +1,55 @@ +package ru.dragonestia.msb3.api.event; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import net.minestom.server.MinecraftServer; +import net.minestom.server.entity.Entity; +import net.minestom.server.entity.Player; +import net.minestom.server.entity.PlayerHand; +import net.minestom.server.event.entity.EntityAttackEvent; +import net.minestom.server.event.player.PlayerEntityInteractEvent; +import net.minestom.server.event.trait.EntityEvent; +import net.minestom.server.event.trait.InstanceEvent; +import net.minestom.server.instance.Instance; +import org.jetbrains.annotations.NotNull; +import ru.dragonestia.msb3.api.entity.NPC; + +import java.util.Objects; + +@RequiredArgsConstructor +public class NPCClickEvent implements EntityEvent, InstanceEvent { + + @Getter private final Player player; + private final NPC npcEntity; + @Getter private final ClickType clickType; + + @Override + public @NotNull Entity getEntity() { + return (Entity) npcEntity; + } + + @Override + public @NotNull Instance getInstance() { + return Objects.requireNonNull(player.getInstance()); + } + + public enum ClickType { + ATTACK, + INTERACT + } + + public static void init() { + MinecraftServer.getGlobalEventHandler().addListener(PlayerEntityInteractEvent.class, event -> { + if (event.getHand() != PlayerHand.MAIN) return; + if (event.getEntity() instanceof NPC npc) { + if (!npc.supportedClickType(ClickType.INTERACT)) return; + MinecraftServer.getGlobalEventHandler().call(new NPCClickEvent(event.getPlayer(), npc, ClickType.INTERACT)); + } + }).addListener(EntityAttackEvent.class, event -> { + if (event.getTarget() instanceof NPC npc && event.getEntity() instanceof Player player) { + if (!npc.supportedClickType(ClickType.ATTACK)) return; + MinecraftServer.getGlobalEventHandler().call(new NPCClickEvent(player, npc, ClickType.ATTACK)); + } + }); + } +} diff --git a/api/src/main/java/ru/dragonestia/msb3/api/glyph/GlyphConstants.java b/api/src/main/java/ru/dragonestia/msb3/api/glyph/GlyphConstants.java deleted file mode 100644 index 2cfc056..0000000 --- a/api/src/main/java/ru/dragonestia/msb3/api/glyph/GlyphConstants.java +++ /dev/null @@ -1,7 +0,0 @@ -package ru.dragonestia.msb3.api.glyph; - -public final class GlyphConstants { - - public static final int CHEST_GUI_WIDTH = 176; - public static final int DEFAULT_CHAT_WIDTH = 320; -} diff --git a/api/src/main/java/ru/dragonestia/msb3/api/glyph/compile/ArbitraryCharacterFactory.java b/api/src/main/java/ru/dragonestia/msb3/api/glyph/compile/ArbitraryCharacterFactory.java deleted file mode 100644 index d380843..0000000 --- a/api/src/main/java/ru/dragonestia/msb3/api/glyph/compile/ArbitraryCharacterFactory.java +++ /dev/null @@ -1,8 +0,0 @@ -package ru.dragonestia.msb3.api.glyph.compile; - -import org.jetbrains.annotations.NotNull; - -public interface ArbitraryCharacterFactory { - - @NotNull Character nextCharacter() throws IllegalStateException; -} diff --git a/api/src/main/java/ru/dragonestia/msb3/api/glyph/compile/DefaultArbitraryCharacterFactory.java b/api/src/main/java/ru/dragonestia/msb3/api/glyph/compile/DefaultArbitraryCharacterFactory.java deleted file mode 100644 index 8462007..0000000 --- a/api/src/main/java/ru/dragonestia/msb3/api/glyph/compile/DefaultArbitraryCharacterFactory.java +++ /dev/null @@ -1,51 +0,0 @@ -package ru.dragonestia.msb3.api.glyph.compile; - -import org.jetbrains.annotations.NotNull; - -import java.util.Arrays; -import java.util.HashSet; -import java.util.Set; - -public class DefaultArbitraryCharacterFactory implements ArbitraryCharacterFactory { - - private final static Set reservedCharacters = new HashSet<>(); - - static { - for (char c = 'a'; c <= 'z'; c++) reservedCharacters.add(c); - for (char c = 'A'; c <= 'Z'; c++) reservedCharacters.add(c); - for (char c = 'а'; c <= 'я'; c++) reservedCharacters.add(c); - for (char c = 'А'; c <= 'Я'; c++) reservedCharacters.add(c); - for (char c = '0'; c <= '9'; c++) reservedCharacters.add(c); - - reservedCharacters.addAll(Arrays.asList( - '!', '?', ':', '$', - ';', '#', '@', '%', - '^', '&', '*', '(', - ')', '_', '-', '+', - '/', '\\', '№', '"', - '\'', '{', '}', '[', - ']', '~', '`', '<', '>', - ',', '.', '|', '\n', '\r', - '\b', '\f', '\t', ' ', - 'ё', 'Ё', '=') - ); - } - - private char currentChar = '\uA201'; - - @Override - public @NotNull Character nextCharacter() throws IllegalStateException { - if (currentChar == Character.MAX_VALUE) { - throw new IllegalStateException("Characters range exceeded"); - } - - do { - currentChar++; - } while (!isCharacterAllowed(currentChar)); - return currentChar; - } - - private boolean isCharacterAllowed(char c) { - return !reservedCharacters.contains(c); - } -} diff --git a/api/src/main/java/ru/dragonestia/msb3/api/glyph/compile/DefaultGlyphCompiler.java b/api/src/main/java/ru/dragonestia/msb3/api/glyph/compile/DefaultGlyphCompiler.java deleted file mode 100644 index 3bdfe65..0000000 --- a/api/src/main/java/ru/dragonestia/msb3/api/glyph/compile/DefaultGlyphCompiler.java +++ /dev/null @@ -1,45 +0,0 @@ -package ru.dragonestia.msb3.api.glyph.compile; - -import net.kyori.adventure.key.Key; -import org.jetbrains.annotations.NotNull; -import team.unnamed.creative.font.Font; -import team.unnamed.creative.font.FontProvider; -import team.unnamed.creative.part.ResourcePackPart; - -import java.util.*; -import java.util.stream.Collectors; -import java.util.stream.Stream; - -public class DefaultGlyphCompiler implements GlyphCompiler { - - @Override - public @NotNull Collection<@NotNull ResourcePackPart> compile(@NotNull Collection<@NotNull ResourceProducer> producers) { - Set fileResources = new HashSet<>(); - - Set fontKeys = producers - .stream() - .map(ResourceProducer::fontKey) - .collect(Collectors.toUnmodifiableSet()); - - for (Key key : fontKeys) { - List fontProviders = new ArrayList<>(); - ArbitraryCharacterFactory characterFactory = new DefaultArbitraryCharacterFactory(); - for (ResourceProducer producer : producers) { - if (producer.fontKey().equals(key)) { - producer.produceResources(characterFactory); - // Add font providers for current font key - fontProviders.addAll(producer.fontProviders()); - // Add textures to common set with resources - fileResources.addAll(producer.textures()); - } - } - fileResources.add(Font.font(key, fontProviders)); - } - - return fileResources; - } - - Iterable toIterable(final Stream stream) { - return stream::iterator; - } -} diff --git a/api/src/main/java/ru/dragonestia/msb3/api/glyph/compile/GlyphCompiler.java b/api/src/main/java/ru/dragonestia/msb3/api/glyph/compile/GlyphCompiler.java deleted file mode 100644 index 2e95249..0000000 --- a/api/src/main/java/ru/dragonestia/msb3/api/glyph/compile/GlyphCompiler.java +++ /dev/null @@ -1,20 +0,0 @@ -package ru.dragonestia.msb3.api.glyph.compile; - -import org.jetbrains.annotations.NotNull; -import team.unnamed.creative.part.ResourcePackPart; - -import java.util.Arrays; -import java.util.Collection; - -public interface GlyphCompiler { - - static GlyphCompiler instance() { - return new DefaultGlyphCompiler(); - } - - @NotNull Collection<@NotNull ResourcePackPart> compile(@NotNull Collection<@NotNull ResourceProducer> resourceProducerCollection); - - default @NotNull Collection<@NotNull ResourcePackPart> compile(@NotNull ResourceProducer... resourceProducer) { - return compile(Arrays.asList(resourceProducer)); - } -} diff --git a/api/src/main/java/ru/dragonestia/msb3/api/glyph/compile/ResourceProducer.java b/api/src/main/java/ru/dragonestia/msb3/api/glyph/compile/ResourceProducer.java deleted file mode 100644 index ad6d90b..0000000 --- a/api/src/main/java/ru/dragonestia/msb3/api/glyph/compile/ResourceProducer.java +++ /dev/null @@ -1,26 +0,0 @@ -package ru.dragonestia.msb3.api.glyph.compile; - -import net.kyori.adventure.key.Key; -import org.jetbrains.annotations.NotNull; -import ru.dragonestia.msb3.api.glyph.glyph.exception.ResourceAlreadyProducedException; -import ru.dragonestia.msb3.api.glyph.glyph.exception.ResourceNotProducedException; -import team.unnamed.creative.font.FontProvider; -import team.unnamed.creative.texture.Texture; - -import java.util.Collection; -import java.util.Collections; - -public interface ResourceProducer { - - @NotNull Key fontKey(); - - boolean produced(); - - void produceResources(ArbitraryCharacterFactory characterFactory) throws ResourceAlreadyProducedException; - - @NotNull Collection<@NotNull FontProvider> fontProviders() throws ResourceNotProducedException; - - default @NotNull Collection<@NotNull Texture> textures() throws ResourceNotProducedException { - return Collections.emptyList(); - } -} 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 deleted file mode 100644 index 2f1640c..0000000 --- a/api/src/main/java/ru/dragonestia/msb3/api/glyph/font/GlyphFont.java +++ /dev/null @@ -1,55 +0,0 @@ -package ru.dragonestia.msb3.api.glyph.font; - -import net.kyori.adventure.key.Key; -import org.jetbrains.annotations.NotNull; -import ru.dragonestia.msb3.api.ServerBootstrap; -import ru.dragonestia.msb3.api.glyph.compile.GlyphCompiler; -import ru.dragonestia.msb3.api.glyph.glyph.Glyph; -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.glyph.space.SpacesGlyphResourceProducer; -import ru.dragonestia.msb3.api.glyph.glyph.space.mojang.MojangSpacesGlyph; -import ru.dragonestia.msb3.api.util.ResourceFromJar; -import team.unnamed.creative.base.Writable; -import team.unnamed.creative.part.ResourcePackPart; -import team.unnamed.creative.texture.Texture; - -import java.util.Collection; -import java.util.List; - -public class GlyphFont { - - public static final SpacesGlyphResourceProducer SPACES = MojangSpacesGlyph.create(); - private static final Key MINECRAFT_FONT_KEY = Key.key(Glyph.DEFAULT_NAMESPACE, "minecraft_font"); - private static final Writable MINECRAFT_FONT_IMAGE_WRITABLE = ResourceFromJar.of(ServerBootstrap.CLASS_LOADER, "glyphs/defaults/minecraft_font.png"); - public static final LanguageGlyphCollection LANGUAGE_GLYPH = minecraftFontGlyphCollection( - MINECRAFT_FONT_KEY, - Key.key(MINECRAFT_FONT_KEY.asString().concat(".png")), - List.of(new TextureProperties(12,-6), - new TextureProperties(8,-24), - new TextureProperties(8,-36))); - - private GlyphFont() {} - - public static Collection compile() { - return GlyphCompiler.instance().compile(SPACES, LANGUAGE_GLYPH); - } - - private static @NotNull LanguageGlyphCollection minecraftFontGlyphCollection(@NotNull Key fontKey, @NotNull Key textureKey, @NotNull List<@NotNull TextureProperties> propertiesList) { - return LanguageGlyphCollection.of(fontKey, - Texture.texture(textureKey, MINECRAFT_FONT_IMAGE_WRITABLE), - propertiesList, - List.of(" АБВГДЕЖЗИК", - "ЛМНОПРСТУФХЦЧШЩЪ", - "ЫЬЭЮЯабвгдежзикл", - "мнопрстуфхцчшщъы", - "ьэюяйЙёЁ ", - "₽!\"#$%&'()*+,-./", - "0123456789: <=>?", - "@ABCDEFGHIJKLMNO", - "PQRSTUVWXYZ[\\]^_", - "`abcdefghijklmno", - "pqrstuvwxyz{|} ") - ); - } -} diff --git a/api/src/main/java/ru/dragonestia/msb3/api/glyph/glyph/AppendableGlyph.java b/api/src/main/java/ru/dragonestia/msb3/api/glyph/glyph/AppendableGlyph.java deleted file mode 100644 index 575546b..0000000 --- a/api/src/main/java/ru/dragonestia/msb3/api/glyph/glyph/AppendableGlyph.java +++ /dev/null @@ -1,3 +0,0 @@ -package ru.dragonestia.msb3.api.glyph.glyph; - -public interface AppendableGlyph extends Glyph {} diff --git a/api/src/main/java/ru/dragonestia/msb3/api/glyph/glyph/EmptyGlyph.java b/api/src/main/java/ru/dragonestia/msb3/api/glyph/glyph/EmptyGlyph.java deleted file mode 100644 index 55b7f45..0000000 --- a/api/src/main/java/ru/dragonestia/msb3/api/glyph/glyph/EmptyGlyph.java +++ /dev/null @@ -1,24 +0,0 @@ -package ru.dragonestia.msb3.api.glyph.glyph; - -import net.kyori.adventure.text.Component; -import org.jetbrains.annotations.NotNull; - -import lombok.AccessLevel; -import lombok.NoArgsConstructor; -import ru.dragonestia.msb3.api.glyph.glyph.exception.ResourceNotProducedException; - -@NoArgsConstructor(access = AccessLevel.PRIVATE) -public class EmptyGlyph implements AppendableGlyph { - - public static final EmptyGlyph INSTANCE = new EmptyGlyph(); - - @Override - public @NotNull Component toAdventure() throws ResourceNotProducedException { - return Component.text(""); - } - - @Override - public int width() { - return 0; - } -} diff --git a/api/src/main/java/ru/dragonestia/msb3/api/glyph/glyph/Glyph.java b/api/src/main/java/ru/dragonestia/msb3/api/glyph/glyph/Glyph.java deleted file mode 100644 index ea4d045..0000000 --- a/api/src/main/java/ru/dragonestia/msb3/api/glyph/glyph/Glyph.java +++ /dev/null @@ -1,21 +0,0 @@ -package ru.dragonestia.msb3.api.glyph.glyph; - -import net.kyori.adventure.key.Key; -import net.kyori.adventure.text.Component; -import org.jetbrains.annotations.NotNull; -import ru.dragonestia.msb3.api.glyph.glyph.exception.ResourceNotProducedException; - -public interface Glyph { - - String DEFAULT_NAMESPACE = "msb3"; - - Key DEFAULT_FONT_KEY = Key.key(DEFAULT_NAMESPACE, "default"); - - Key DEFAULT_SPACES_FONT_KEY = Key.key(DEFAULT_NAMESPACE, "spaces"); - - int SEPARATOR_WIDTH = 1; - - @NotNull Component toAdventure() throws ResourceNotProducedException; - - int width(); -} diff --git a/api/src/main/java/ru/dragonestia/msb3/api/glyph/glyph/GlyphComponentBuilder.java b/api/src/main/java/ru/dragonestia/msb3/api/glyph/glyph/GlyphComponentBuilder.java deleted file mode 100644 index 16d9e96..0000000 --- a/api/src/main/java/ru/dragonestia/msb3/api/glyph/glyph/GlyphComponentBuilder.java +++ /dev/null @@ -1,60 +0,0 @@ -package ru.dragonestia.msb3.api.glyph.glyph; - -import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.format.NamedTextColor; -import org.jetbrains.annotations.NotNull; -import ru.dragonestia.msb3.api.glyph.glyph.space.SpacesGlyphResourceProducer; - -import java.util.List; - -public interface GlyphComponentBuilder { - - static @NotNull GlyphComponentBuilder universal(SpacesGlyphResourceProducer spacesProducer) { - return new GlyphComponentBuilderImpl(spacesProducer, 0, Component.text("")); - } - - static @NotNull GlyphComponentBuilder gui(SpacesGlyphResourceProducer spacesProducer) { - return new GlyphComponentBuilderImpl(spacesProducer, -8, Component.text("", NamedTextColor.WHITE)); - } - - @NotNull GlyphComponentBuilder append(PositionType positionType, int position, @NotNull AppendableGlyph glyph); - - default @NotNull GlyphComponentBuilder append(PositionType positionType, @NotNull AppendableGlyph glyph) { - return append(positionType, 0, glyph); - } - - @NotNull GlyphComponentBuilder append(PositionType positionType, int position, @NotNull List glyphList); - - default @NotNull GlyphComponentBuilder append(PositionType positionType, @NotNull List glyphList) { - return append(positionType, 0, glyphList); - } - - // Append with default position type - - default @NotNull GlyphComponentBuilder append(int position, @NotNull AppendableGlyph glyph) { - return append(PositionType.ABSOLUTE, position, glyph); - } - - default @NotNull GlyphComponentBuilder append(@NotNull AppendableGlyph glyph) { - return append(PositionType.ABSOLUTE, glyph); - } - - default @NotNull GlyphComponentBuilder append(int position, @NotNull List glyphList) { - return append(PositionType.ABSOLUTE, position, glyphList); - } - - default @NotNull GlyphComponentBuilder append(@NotNull List glyphList) { - return append(PositionType.ABSOLUTE, glyphList); - } - - @NotNull Component build(boolean keepInitialPosition); - - default @NotNull Component build() { - return build(true); - } - - enum PositionType { - ABSOLUTE, - RELATIVE - } -} diff --git a/api/src/main/java/ru/dragonestia/msb3/api/glyph/glyph/GlyphComponentBuilderImpl.java b/api/src/main/java/ru/dragonestia/msb3/api/glyph/glyph/GlyphComponentBuilderImpl.java deleted file mode 100644 index ffc0815..0000000 --- a/api/src/main/java/ru/dragonestia/msb3/api/glyph/glyph/GlyphComponentBuilderImpl.java +++ /dev/null @@ -1,86 +0,0 @@ -package ru.dragonestia.msb3.api.glyph.glyph; - -import net.kyori.adventure.text.Component; -import org.jetbrains.annotations.NotNull; - -import lombok.AccessLevel; -import lombok.RequiredArgsConstructor; -import ru.dragonestia.msb3.api.glyph.glyph.space.SpacesGlyphResourceProducer; - -import java.util.ArrayList; -import java.util.List; - -@RequiredArgsConstructor(access = AccessLevel.PACKAGE) -public class GlyphComponentBuilderImpl implements GlyphComponentBuilder { - - private final SpacesGlyphResourceProducer spacesProducer; - private final int initialPosition; - private final Component baseComponent; - - private final List glyphs = new ArrayList<>(); - - private int previousElementsWidth; - - @Override - public @NotNull GlyphComponentBuilder append(PositionType positionType, int position, @NotNull AppendableGlyph glyph) { - if (positionType == PositionType.ABSOLUTE && previousElementsWidth != 0) { - glyphs.add(spacesProducer.translate((-1) * previousElementsWidth)); - previousElementsWidth = 0; - } - - if (position != 0) { - glyphs.add(spacesProducer.translate(position)); - } - - glyphs.add(glyph); - this.previousElementsWidth += position + glyph.width(); - - return this; - } - - @Override - public @NotNull GlyphComponentBuilder append(PositionType positionType, int position, @NotNull List glyphList) { - if (positionType == PositionType.ABSOLUTE && previousElementsWidth != 0) { - glyphs.add(spacesProducer.translate((-1) * previousElementsWidth)); - previousElementsWidth = 0; - } - - if (position != 0) { - glyphs.add(spacesProducer.translate(position)); - } - - int width = 0; - for (AppendableGlyph glyph : glyphList) { - glyphs.add(glyph); - width += glyph.width(); - } - - this.previousElementsWidth += position + width; - - return this; - } - - @Override - public @NotNull Component build(boolean keepInitialPosition) { - if (keepInitialPosition) { - previousElementsWidth += initialPosition; - - // Component should have zero width finally - if (previousElementsWidth != 0) { - glyphs.add(spacesProducer.translate((-1) * previousElementsWidth)); - } - } - - var component = baseComponent; - - if (initialPosition != 0) { - component = component.append(spacesProducer.translate(initialPosition).toAdventure()); - } - - for (Glyph glyph : glyphs) { - component = component.append(glyph.toAdventure()); - } - - return component; - } -} diff --git a/api/src/main/java/ru/dragonestia/msb3/api/glyph/glyph/exception/ResourceAlreadyProducedException.java b/api/src/main/java/ru/dragonestia/msb3/api/glyph/glyph/exception/ResourceAlreadyProducedException.java deleted file mode 100644 index 3fd0416..0000000 --- a/api/src/main/java/ru/dragonestia/msb3/api/glyph/glyph/exception/ResourceAlreadyProducedException.java +++ /dev/null @@ -1,3 +0,0 @@ -package ru.dragonestia.msb3.api.glyph.glyph.exception; - -public class ResourceAlreadyProducedException extends RuntimeException {} diff --git a/api/src/main/java/ru/dragonestia/msb3/api/glyph/glyph/exception/ResourceNotProducedException.java b/api/src/main/java/ru/dragonestia/msb3/api/glyph/glyph/exception/ResourceNotProducedException.java deleted file mode 100644 index 8a66085..0000000 --- a/api/src/main/java/ru/dragonestia/msb3/api/glyph/glyph/exception/ResourceNotProducedException.java +++ /dev/null @@ -1,3 +0,0 @@ -package ru.dragonestia.msb3.api.glyph.glyph.exception; - -public class ResourceNotProducedException extends RuntimeException {} diff --git a/api/src/main/java/ru/dragonestia/msb3/api/glyph/glyph/image/ImageGlyph.java b/api/src/main/java/ru/dragonestia/msb3/api/glyph/glyph/image/ImageGlyph.java deleted file mode 100644 index 6f071f4..0000000 --- a/api/src/main/java/ru/dragonestia/msb3/api/glyph/glyph/image/ImageGlyph.java +++ /dev/null @@ -1,33 +0,0 @@ -package ru.dragonestia.msb3.api.glyph.glyph.image; - -import net.kyori.adventure.key.Key; -import net.kyori.adventure.text.Component; -import org.jetbrains.annotations.NotNull; -import ru.dragonestia.msb3.api.glyph.compile.ResourceProducer; -import ru.dragonestia.msb3.api.glyph.glyph.AppendableGlyph; -import ru.dragonestia.msb3.api.glyph.glyph.Glyph; -import ru.dragonestia.msb3.api.glyph.glyph.exception.ResourceNotProducedException; -import team.unnamed.creative.texture.Texture; - -public interface ImageGlyph extends AppendableGlyph, ResourceProducer { - - static @NotNull ImageGlyph of(@NotNull Key key, - @NotNull Texture texture, - @NotNull TextureProperties properties) { - return new ImageGlyphImpl(key, texture, properties); - } - - @Deprecated(forRemoval = true) - static @NotNull ImageGlyph of(@NotNull Texture texture, - @NotNull TextureProperties properties) { - return of(Glyph.DEFAULT_FONT_KEY, texture, properties); - } - - @NotNull Character character() throws ResourceNotProducedException; - - @NotNull Texture texture(); - - default @NotNull Component toAdventure() throws ResourceNotProducedException { - return Component.text(character()).font(fontKey()); - } -} diff --git a/api/src/main/java/ru/dragonestia/msb3/api/glyph/glyph/image/ImageGlyphImpl.java b/api/src/main/java/ru/dragonestia/msb3/api/glyph/glyph/image/ImageGlyphImpl.java deleted file mode 100644 index fd2bdcc..0000000 --- a/api/src/main/java/ru/dragonestia/msb3/api/glyph/glyph/image/ImageGlyphImpl.java +++ /dev/null @@ -1,105 +0,0 @@ -package ru.dragonestia.msb3.api.glyph.glyph.image; - -import net.kyori.adventure.key.Key; -import org.jetbrains.annotations.NotNull; -import ru.dragonestia.msb3.api.glyph.compile.ArbitraryCharacterFactory; -import ru.dragonestia.msb3.api.glyph.glyph.Glyph; -import ru.dragonestia.msb3.api.glyph.glyph.exception.ResourceAlreadyProducedException; -import ru.dragonestia.msb3.api.glyph.glyph.exception.ResourceNotProducedException; -import ru.dragonestia.msb3.api.glyph.util.ImageUtil; -import team.unnamed.creative.font.FontProvider; -import team.unnamed.creative.texture.Texture; - -import lombok.AccessLevel; -import lombok.RequiredArgsConstructor; - -import javax.imageio.ImageIO; -import java.awt.image.BufferedImage; -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.util.Collection; -import java.util.Collections; -import java.util.Set; - -@RequiredArgsConstructor(access = AccessLevel.PACKAGE) -public class ImageGlyphImpl implements ImageGlyph { - - private final Key fontKey; - private final Texture texture; - private final TextureProperties properties; - - private Character character; - private Set fontProviders; - - private int width = -1; - - @Override - public @NotNull Key fontKey() { - return fontKey; - } - - @Override - public boolean produced() { - return fontProviders != null; - } - - @Override - public void produceResources(ArbitraryCharacterFactory characterFactory) throws ResourceAlreadyProducedException { - if (fontProviders != null) { - throw new ResourceAlreadyProducedException(); - } - - var fontProviderBuilder = FontProvider.bitMap(); - - this.character = characterFactory.nextCharacter(); - - fontProviderBuilder.characters(String.valueOf(character)); - fontProviderBuilder.file(texture.key()); - fontProviderBuilder.ascent(properties.ascent()); - fontProviderBuilder.height(properties.height()); - - this.fontProviders = Collections.singleton(fontProviderBuilder.build()); - } - - @Override - public @NotNull Collection<@NotNull FontProvider> fontProviders() throws ResourceNotProducedException { - if (fontProviders == null) { - throw new ResourceNotProducedException(); - } - return fontProviders; - } - - @Override - public @NotNull Collection<@NotNull Texture> textures() throws ResourceNotProducedException { - return Collections.singleton(texture); - } - - @Override - public int width() { - if (width == -1) { - try { - BufferedImage image = ImageIO.read(new ByteArrayInputStream(texture.data().toByteArray())); - int fileHeight = image.getHeight(); - width = (int) Math.ceil( - ((double) properties.height() / (double) fileHeight) - * ImageUtil.calculateWidth(image)) + Glyph.SEPARATOR_WIDTH; - } catch (IOException e) { - e.printStackTrace(); - } - } - return width; - } - - @Override - public @NotNull Character character() throws ResourceNotProducedException { - if (fontProviders == null) { - throw new ResourceNotProducedException(); - } - return character; - } - - @Override - public @NotNull Texture texture() { - return texture; - } -} diff --git a/api/src/main/java/ru/dragonestia/msb3/api/glyph/glyph/image/multicharacter/ColorableGlyph.java b/api/src/main/java/ru/dragonestia/msb3/api/glyph/glyph/image/multicharacter/ColorableGlyph.java deleted file mode 100644 index 55b3ddf..0000000 --- a/api/src/main/java/ru/dragonestia/msb3/api/glyph/glyph/image/multicharacter/ColorableGlyph.java +++ /dev/null @@ -1,11 +0,0 @@ -package ru.dragonestia.msb3.api.glyph.glyph.image.multicharacter; - -import net.kyori.adventure.text.format.TextColor; -import org.jetbrains.annotations.Nullable; - -public interface ColorableGlyph { - - @Nullable TextColor color(); - - void updateColor(@Nullable TextColor color); -} 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 deleted file mode 100644 index 3208d92..0000000 --- a/api/src/main/java/ru/dragonestia/msb3/api/glyph/glyph/image/multicharacter/LanguageGlyphCollection.java +++ /dev/null @@ -1,42 +0,0 @@ -package ru.dragonestia.msb3.api.glyph.glyph.image.multicharacter; - -import net.kyori.adventure.key.Key; -import net.kyori.adventure.text.format.TextColor; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import ru.dragonestia.msb3.api.glyph.compile.ResourceProducer; -import ru.dragonestia.msb3.api.glyph.glyph.AppendableGlyph; -import ru.dragonestia.msb3.api.glyph.glyph.Glyph; -import ru.dragonestia.msb3.api.glyph.glyph.image.TextureProperties; -import team.unnamed.creative.texture.Texture; - -import java.util.List; - -public interface LanguageGlyphCollection extends ResourceProducer { - - static @NotNull LanguageGlyphCollection of(@NotNull Key fontKey, - @NotNull Texture texture, - @NotNull List<@NotNull TextureProperties> propertiesList, - @NotNull List<@NotNull String> charactersMapping) { - return new LanguageGlyphCollectionImpl(fontKey, texture, propertiesList, charactersMapping); - } - - @Deprecated(forRemoval = true) - static @NotNull LanguageGlyphCollection of(@NotNull Texture texture, - @NotNull List<@NotNull TextureProperties> propertiesList, - @NotNull List<@NotNull String> charactersMapping) { - return of(Glyph.DEFAULT_FONT_KEY, texture, propertiesList, charactersMapping); - } - - @NotNull AppendableGlyph translate(int height, int ascent, @NotNull Character character, @Nullable TextColor color) throws IllegalArgumentException; - - @NotNull List<@NotNull AppendableGlyph> translate(int height, int ascent, @NotNull String text, @Nullable TextColor color) throws IllegalArgumentException; - - default @NotNull AppendableGlyph translate(int height, int ascent, @NotNull Character character) throws IllegalArgumentException { - return translate(height, ascent, character, null); - } - - default @NotNull List<@NotNull AppendableGlyph> translate(int height, int ascent, @NotNull String text) throws IllegalArgumentException { - return translate(height, ascent, text, 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 deleted file mode 100644 index 36eba45..0000000 --- a/api/src/main/java/ru/dragonestia/msb3/api/glyph/glyph/image/multicharacter/LanguageGlyphCollectionImpl.java +++ /dev/null @@ -1,92 +0,0 @@ -package ru.dragonestia.msb3.api.glyph.glyph.image.multicharacter; - -import net.kyori.adventure.key.Key; -import net.kyori.adventure.text.format.TextColor; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import ru.dragonestia.msb3.api.glyph.compile.ArbitraryCharacterFactory; -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 team.unnamed.creative.font.FontProvider; -import team.unnamed.creative.texture.Texture; - -import java.util.*; - -public class LanguageGlyphCollectionImpl implements LanguageGlyphCollection { - - private final Key fontKey; - private final Texture texture; - - private final Map propertiesToMulticharacterMap = new HashMap<>(); - - private Set fontProviders; - - LanguageGlyphCollectionImpl( - Key fontKey, - Texture texture, - List propertiesList, - List charactersMapping - ) { - this.fontKey = fontKey; - this.texture = texture; - - for (TextureProperties properties : propertiesList) { - propertiesToMulticharacterMap.put(properties, MulticharacterImageGlyphCollection.of(fontKey, texture, properties, charactersMapping)); - } - } - - - @Override - public @NotNull Key fontKey() { - return fontKey; - } - - @Override - public boolean produced() { - return fontProviders != null; - } - - @Override - public void produceResources(ArbitraryCharacterFactory characterFactory) throws ResourceAlreadyProducedException { - if (fontProviders != null) { - throw new ResourceAlreadyProducedException(); - } - - fontProviders = new HashSet<>(); - - for (var entry : propertiesToMulticharacterMap.entrySet()) { - entry.getValue().produceResources(characterFactory); - fontProviders.addAll(entry.getValue().fontProviders()); - } - } - - @Override - public @NotNull Collection<@NotNull FontProvider> fontProviders() throws ResourceNotProducedException { - return fontProviders; - } - - @Override - public @NotNull Collection<@NotNull Texture> textures() throws ResourceNotProducedException { - return Collections.singleton(texture); - } - - @Override - public @NotNull AppendableGlyph translate(int height, int ascent, @NotNull Character character, @Nullable TextColor color) throws IllegalArgumentException { - TextureProperties properties = new TextureProperties(height, ascent); - if (!propertiesToMulticharacterMap.containsKey(properties)) { - throw new IllegalArgumentException(); - } - return propertiesToMulticharacterMap.get(properties).translate(character, color); - } - - @Override - 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(); - } - return propertiesToMulticharacterMap.get(properties).translate(text, color); - } -} diff --git a/api/src/main/java/ru/dragonestia/msb3/api/glyph/glyph/image/multicharacter/MulticharacterImageGlyphCollection.java b/api/src/main/java/ru/dragonestia/msb3/api/glyph/glyph/image/multicharacter/MulticharacterImageGlyphCollection.java deleted file mode 100644 index 8e2f8e2..0000000 --- a/api/src/main/java/ru/dragonestia/msb3/api/glyph/glyph/image/multicharacter/MulticharacterImageGlyphCollection.java +++ /dev/null @@ -1,54 +0,0 @@ -package ru.dragonestia.msb3.api.glyph.glyph.image.multicharacter; - -import net.kyori.adventure.key.Key; -import net.kyori.adventure.text.format.TextColor; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import ru.dragonestia.msb3.api.glyph.compile.ResourceProducer; -import ru.dragonestia.msb3.api.glyph.glyph.AppendableGlyph; -import ru.dragonestia.msb3.api.glyph.glyph.Glyph; -import ru.dragonestia.msb3.api.glyph.glyph.image.TextureProperties; -import ru.dragonestia.msb3.api.glyph.glyph.space.SpacesGlyph; -import team.unnamed.creative.texture.Texture; - -import java.util.ArrayList; -import java.util.List; - -public interface MulticharacterImageGlyphCollection extends ResourceProducer { - - static @NotNull MulticharacterImageGlyphCollection of(@NotNull Key fontKey, - @NotNull Texture texture, - @NotNull TextureProperties properties, - @NotNull List<@NotNull String> charactersMapping) { - return new MulticharacterImageGlyphCollectionImpl(fontKey, texture, properties, charactersMapping); - } - - @Deprecated(forRemoval = true) - static @NotNull MulticharacterImageGlyphCollection of(@NotNull Texture texture, - @NotNull TextureProperties properties, - @NotNull List<@NotNull String> charactersMapping) { - return of(Glyph.DEFAULT_FONT_KEY, texture, properties, charactersMapping); - } - - @NotNull AppendableGlyph translate(@NotNull Character character, @Nullable TextColor color) throws IllegalArgumentException; - - default @NotNull List<@NotNull AppendableGlyph> translate(@NotNull String text, @Nullable TextColor color) throws IllegalArgumentException { - List glyphs = new ArrayList<>(); - for (char character : text.toCharArray()) { - if (character == ' ') { - glyphs.add(SpacesGlyph.DEFAULT_SPACE_GLYPH); - } else { - glyphs.add(translate(character, color)); - } - } - return glyphs; - } - - default @NotNull AppendableGlyph translate(@NotNull Character character) throws IllegalArgumentException { - return translate(character, null); - } - - default @NotNull List<@NotNull AppendableGlyph> translate(@NotNull String text) throws IllegalArgumentException { - return translate(text, null); - } -} diff --git a/api/src/main/java/ru/dragonestia/msb3/api/glyph/glyph/image/multicharacter/MulticharacterImageGlyphCollectionImpl.java b/api/src/main/java/ru/dragonestia/msb3/api/glyph/glyph/image/multicharacter/MulticharacterImageGlyphCollectionImpl.java deleted file mode 100644 index b7879b4..0000000 --- a/api/src/main/java/ru/dragonestia/msb3/api/glyph/glyph/image/multicharacter/MulticharacterImageGlyphCollectionImpl.java +++ /dev/null @@ -1,131 +0,0 @@ -package ru.dragonestia.msb3.api.glyph.glyph.image.multicharacter; - -import net.kyori.adventure.key.Key; -import net.kyori.adventure.text.format.TextColor; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import ru.dragonestia.msb3.api.glyph.compile.ArbitraryCharacterFactory; -import ru.dragonestia.msb3.api.glyph.glyph.Glyph; -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.ImageUtil; -import team.unnamed.creative.font.FontProvider; -import team.unnamed.creative.texture.Texture; - -import lombok.AccessLevel; -import lombok.RequiredArgsConstructor; - -import javax.imageio.ImageIO; -import java.awt.image.BufferedImage; -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.util.*; - -@RequiredArgsConstructor(access = AccessLevel.PACKAGE) -public class MulticharacterImageGlyphCollectionImpl implements MulticharacterImageGlyphCollection { - - private final Key fontKey; - private final Texture texture; - private final TextureProperties properties; - private final List charactersMapping; - - private final Map originToArbitraryCharacterMap = new HashMap<>(); - - private Set fontProviders; - - private BufferedImage image; - - @Override - public @NotNull Key fontKey() { - return fontKey; - } - - @Override - public boolean produced() { - return fontProviders != null; - } - - @Override - public void produceResources(ArbitraryCharacterFactory characterFactory) throws ResourceAlreadyProducedException { - if (fontProviders != null) { - throw new ResourceAlreadyProducedException(); - } - - var fontProviderBuilder = FontProvider.bitMap(); - fontProviderBuilder.file(texture.key()); - fontProviderBuilder.ascent(properties.ascent()); - fontProviderBuilder.height(properties.height()); - - List mappingLines = new ArrayList<>(); - - for (String mappingLine : charactersMapping) { - StringBuilder builder = new StringBuilder(); - for (char character : mappingLine.toCharArray()) { - char arbitraryCharacter = characterFactory.nextCharacter(); - originToArbitraryCharacterMap.put(character, arbitraryCharacter); - builder.append(arbitraryCharacter); - } - mappingLines.add(builder.toString()); - } - - fontProviderBuilder.characters(mappingLines); - - this.fontProviders = Collections.singleton(fontProviderBuilder.build()); - } - - @Override - public @NotNull Collection<@NotNull FontProvider> fontProviders() throws ResourceNotProducedException { - return fontProviders; - } - - @Override - public @NotNull Collection<@NotNull Texture> textures() throws ResourceNotProducedException { - return Collections.singleton(texture); - } - - @Override - public @NotNull PreparedImageGlyph translate(@NotNull Character character, @Nullable TextColor color) throws IllegalArgumentException { - if (!originToArbitraryCharacterMap.containsKey(character)) { - throw new IllegalArgumentException(); - } - - int width = 0; - for (int lineIndex = 0; lineIndex < charactersMapping.size(); lineIndex++) { - String line = charactersMapping.get(lineIndex); - for (int characterIndex = 0; characterIndex < line.toCharArray().length; characterIndex++) { - if (line.charAt(characterIndex) == character) { - if (image == null) { - cacheImage(); - } - - if (image == null) { - throw new IllegalArgumentException(); - } - - int filePartWidth = image.getWidth() / charactersMapping.get(0).length(); - int filePartHeight = image.getHeight() / charactersMapping.size(); - - width = (int) Math.ceil( - ((double) properties.height() / (double) filePartHeight) - * ImageUtil.calculateWidth( - image, filePartWidth * characterIndex, filePartHeight * lineIndex, - filePartWidth * (characterIndex + 1), filePartHeight * (lineIndex + 1) - )) + Glyph.SEPARATOR_WIDTH; - break; - } - } - } - - return new PreparedImageGlyph(fontKey, originToArbitraryCharacterMap.get(character), width, color); - } - - private void cacheImage() { - try { - image = ImageIO.read(new ByteArrayInputStream(texture.data().toByteArray())); - } catch (IOException e) { - e.printStackTrace(); - } - } - -} diff --git a/api/src/main/java/ru/dragonestia/msb3/api/glyph/glyph/image/multicharacter/PreparedImageGlyph.java b/api/src/main/java/ru/dragonestia/msb3/api/glyph/glyph/image/multicharacter/PreparedImageGlyph.java deleted file mode 100644 index c8d2d89..0000000 --- a/api/src/main/java/ru/dragonestia/msb3/api/glyph/glyph/image/multicharacter/PreparedImageGlyph.java +++ /dev/null @@ -1,43 +0,0 @@ -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.NamedTextColor; -import net.kyori.adventure.text.format.TextColor; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import lombok.AccessLevel; -import lombok.AllArgsConstructor; -import ru.dragonestia.msb3.api.glyph.glyph.AppendableGlyph; - -@AllArgsConstructor(access = AccessLevel.PACKAGE) -public class PreparedImageGlyph implements AppendableGlyph, ColorableGlyph { - - private final Key key; - private final Character character; - private final int width; - private @Nullable TextColor color; - - @Override - public @NotNull Component toAdventure() { - return Component.text(character) - .font(key) - .color(color == null ? NamedTextColor.BLACK : color); - } - - @Override - public int width() { - return width; - } - - @Override - public @Nullable TextColor color() { - return color; - } - - @Override - public void updateColor(@Nullable TextColor color) { - this.color = color; - } -} diff --git a/api/src/main/java/ru/dragonestia/msb3/api/glyph/glyph/space/AbstractSpacesGlyphResourceProducer.java b/api/src/main/java/ru/dragonestia/msb3/api/glyph/glyph/space/AbstractSpacesGlyphResourceProducer.java deleted file mode 100644 index 1c42e8e..0000000 --- a/api/src/main/java/ru/dragonestia/msb3/api/glyph/glyph/space/AbstractSpacesGlyphResourceProducer.java +++ /dev/null @@ -1,64 +0,0 @@ -package ru.dragonestia.msb3.api.glyph.glyph.space; - -import net.kyori.adventure.key.Key; -import org.jetbrains.annotations.NotNull; - -import lombok.RequiredArgsConstructor; -import ru.dragonestia.msb3.api.glyph.glyph.EmptyGlyph; -import ru.dragonestia.msb3.api.glyph.glyph.Glyph; -import ru.dragonestia.msb3.api.glyph.glyph.exception.ResourceNotProducedException; -import ru.dragonestia.msb3.api.glyph.util.ArrayUtil; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - -@RequiredArgsConstructor -public abstract class AbstractSpacesGlyphResourceProducer implements SpacesGlyphResourceProducer { - - private final Key fontKey; - - protected Map mapping; - - @Override - public @NotNull Key fontKey() { - return fontKey; - } - - @Override - public boolean produced() { - return mapping != null; - } - - @Override - public Glyph translate(int length) throws ResourceNotProducedException { - if (mapping == null) { - throw new ResourceNotProducedException(); - } - - if (length == 0) { - return EmptyGlyph.INSTANCE; - } - - int sign = length > 0 ? 1 : -1; - String binaryString = Integer.toBinaryString(Math.abs(length)); - - List characters = new ArrayList<>(); - - int currentRankLength = 1; - for (int index = 0; index < binaryString.length(); index++) { - char digit = binaryString.charAt(binaryString.length() - index - 1); - if (digit == '1') { - int partLength = currentRankLength * sign; - if (!mapping.containsKey(partLength)) { - throw new IllegalArgumentException("Too much length"); - } - characters.add(mapping.get(partLength)); - } - currentRankLength *= 2; - } - - return new SpacesGlyph(fontKey(), ArrayUtil.toCharArray(characters), length); - } - -} diff --git a/api/src/main/java/ru/dragonestia/msb3/api/glyph/glyph/space/DefaultSpaceGlyph.java b/api/src/main/java/ru/dragonestia/msb3/api/glyph/glyph/space/DefaultSpaceGlyph.java deleted file mode 100644 index 4d38585..0000000 --- a/api/src/main/java/ru/dragonestia/msb3/api/glyph/glyph/space/DefaultSpaceGlyph.java +++ /dev/null @@ -1,24 +0,0 @@ -package ru.dragonestia.msb3.api.glyph.glyph.space; - -import net.kyori.adventure.text.Component; -import org.jetbrains.annotations.NotNull; -import ru.dragonestia.msb3.api.glyph.glyph.AppendableGlyph; -import ru.dragonestia.msb3.api.glyph.glyph.exception.ResourceNotProducedException; -import team.unnamed.creative.font.Font; - -import lombok.AccessLevel; -import lombok.NoArgsConstructor; - -@NoArgsConstructor(access = AccessLevel.PACKAGE) -public class DefaultSpaceGlyph implements AppendableGlyph { - - @Override - public @NotNull Component toAdventure() throws ResourceNotProducedException { - return Component.text(" ").font(Font.MINECRAFT_DEFAULT); - } - - @Override - public int width() { - return 4; - } -} diff --git a/api/src/main/java/ru/dragonestia/msb3/api/glyph/glyph/space/DefaultSpacesGlyphResourceProducer.java b/api/src/main/java/ru/dragonestia/msb3/api/glyph/glyph/space/DefaultSpacesGlyphResourceProducer.java deleted file mode 100644 index 27fb036..0000000 --- a/api/src/main/java/ru/dragonestia/msb3/api/glyph/glyph/space/DefaultSpacesGlyphResourceProducer.java +++ /dev/null @@ -1,91 +0,0 @@ -package ru.dragonestia.msb3.api.glyph.glyph.space; - -import net.kyori.adventure.key.Key; -import org.jetbrains.annotations.NotNull; -import ru.dragonestia.msb3.api.glyph.compile.ArbitraryCharacterFactory; -import ru.dragonestia.msb3.api.glyph.glyph.exception.ResourceAlreadyProducedException; -import ru.dragonestia.msb3.api.glyph.glyph.exception.ResourceNotProducedException; -import team.unnamed.creative.base.Writable; -import team.unnamed.creative.font.BitMapFontProvider; -import team.unnamed.creative.font.FontProvider; -import team.unnamed.creative.texture.Texture; - -import java.util.*; - -public class DefaultSpacesGlyphResourceProducer extends AbstractSpacesGlyphResourceProducer { - - private final Key textureKey; - private final Writable writable; - - private Set textures; - private Set fontProviders; - - public DefaultSpacesGlyphResourceProducer(Key fontKey, Key textureKey, Writable writable) { - super(fontKey); - this.textureKey = textureKey; - this.writable = writable; - } - - @Override - public boolean produced() { - return textures != null; - } - - @Override - public void produceResources(ArbitraryCharacterFactory characterFactory) { - if (textures != null) { - throw new ResourceAlreadyProducedException(); - } - - this.mapping = new HashMap<>(); - - Set fontProviders = new HashSet<>(); - - for (int length = 1; length <= 2048; length *= 2) { - fontProviders.add(prepareBuilder(characterFactory, length).build()); - fontProviders.add(prepareBuilder(characterFactory, length * (-1)).build()); - } - - this.textures = Collections.singleton(Texture.texture(textureKey, writable)); - this.fontProviders = fontProviders; - } - - @Override - public @NotNull Collection<@NotNull FontProvider> fontProviders() throws ResourceNotProducedException { - if (fontProviders == null) { - throw new ResourceNotProducedException(); - } - return fontProviders; - } - - @Override - public @NotNull Collection<@NotNull Texture> textures() throws ResourceNotProducedException { - if (textures == null) { - throw new ResourceNotProducedException(); - } - return textures; - } - - @NotNull - private BitMapFontProvider.Builder prepareBuilder(ArbitraryCharacterFactory characterFactory, int length) { - var fontProviderBuilder = FontProvider.bitMap(); - - char character = characterFactory.nextCharacter(); - - fontProviderBuilder.characters(String.valueOf(character)); - fontProviderBuilder.file(textureKey); - - if (length > 0) { - fontProviderBuilder.height(length - 1); - fontProviderBuilder.ascent(0); - } else { - fontProviderBuilder.height(length - 2); - fontProviderBuilder.ascent(-32768); - } - - mapping.put(length, character); - - return fontProviderBuilder; - } - -} diff --git a/api/src/main/java/ru/dragonestia/msb3/api/glyph/glyph/space/SpacesGlyph.java b/api/src/main/java/ru/dragonestia/msb3/api/glyph/glyph/space/SpacesGlyph.java deleted file mode 100644 index 5c9fa41..0000000 --- a/api/src/main/java/ru/dragonestia/msb3/api/glyph/glyph/space/SpacesGlyph.java +++ /dev/null @@ -1,40 +0,0 @@ -package ru.dragonestia.msb3.api.glyph.glyph.space; - -import net.kyori.adventure.key.Key; -import net.kyori.adventure.text.Component; -import org.jetbrains.annotations.NotNull; -import ru.dragonestia.msb3.api.glyph.glyph.Glyph; -import team.unnamed.creative.base.Writable; - -import lombok.AccessLevel; -import lombok.RequiredArgsConstructor; - -@RequiredArgsConstructor(access = AccessLevel.PACKAGE) -public class SpacesGlyph implements Glyph { - - private static final Key DEFAULT_SPACE_TEXTURE_KEY = Key.key(Glyph.DEFAULT_NAMESPACE, "space"); - public static final @NotNull DefaultSpaceGlyph DEFAULT_SPACE_GLYPH = new DefaultSpaceGlyph(); - private final Key key; - private final char[] characters; - private final int length; - - public static @NotNull SpacesGlyphResourceProducer create(@NotNull Key fontKey, - @NotNull Key textureKey, - @NotNull Writable spacesWritable) { - return new DefaultSpacesGlyphResourceProducer(fontKey, textureKey, spacesWritable); - } - - public static SpacesGlyphResourceProducer create(@NotNull Writable spacesWritable) { - return create(Glyph.DEFAULT_SPACES_FONT_KEY, DEFAULT_SPACE_TEXTURE_KEY, spacesWritable); - } - - @Override - public @NotNull Component toAdventure() { - return Component.text(new String(characters)).font(key); - } - - @Override - public int width() { - return length; - } -} diff --git a/api/src/main/java/ru/dragonestia/msb3/api/glyph/glyph/space/SpacesGlyphResourceProducer.java b/api/src/main/java/ru/dragonestia/msb3/api/glyph/glyph/space/SpacesGlyphResourceProducer.java deleted file mode 100644 index 9b8ca87..0000000 --- a/api/src/main/java/ru/dragonestia/msb3/api/glyph/glyph/space/SpacesGlyphResourceProducer.java +++ /dev/null @@ -1,10 +0,0 @@ -package ru.dragonestia.msb3.api.glyph.glyph.space; - -import ru.dragonestia.msb3.api.glyph.compile.ResourceProducer; -import ru.dragonestia.msb3.api.glyph.glyph.Glyph; -import ru.dragonestia.msb3.api.glyph.glyph.exception.ResourceNotProducedException; - -public interface SpacesGlyphResourceProducer extends ResourceProducer { - - Glyph translate(int length) throws ResourceNotProducedException; -} diff --git a/api/src/main/java/ru/dragonestia/msb3/api/glyph/glyph/space/mojang/MojangSpacesGlyph.java b/api/src/main/java/ru/dragonestia/msb3/api/glyph/glyph/space/mojang/MojangSpacesGlyph.java deleted file mode 100644 index 1901886..0000000 --- a/api/src/main/java/ru/dragonestia/msb3/api/glyph/glyph/space/mojang/MojangSpacesGlyph.java +++ /dev/null @@ -1,18 +0,0 @@ -package ru.dragonestia.msb3.api.glyph.glyph.space.mojang; - -import net.kyori.adventure.key.Key; -import org.jetbrains.annotations.NotNull; -import ru.dragonestia.msb3.api.glyph.compile.ResourceProducer; -import ru.dragonestia.msb3.api.glyph.glyph.Glyph; -import ru.dragonestia.msb3.api.glyph.glyph.space.SpacesGlyphResourceProducer; - -public interface MojangSpacesGlyph extends Glyph, ResourceProducer { - - static @NotNull SpacesGlyphResourceProducer create(@NotNull Key key) { - return new MojangSpacesGlyphResourceProducer(key); - } - - static SpacesGlyphResourceProducer create() { - return create(Glyph.DEFAULT_SPACES_FONT_KEY); - } -} diff --git a/api/src/main/java/ru/dragonestia/msb3/api/glyph/glyph/space/mojang/MojangSpacesGlyphResourceProducer.java b/api/src/main/java/ru/dragonestia/msb3/api/glyph/glyph/space/mojang/MojangSpacesGlyphResourceProducer.java deleted file mode 100644 index eccef64..0000000 --- a/api/src/main/java/ru/dragonestia/msb3/api/glyph/glyph/space/mojang/MojangSpacesGlyphResourceProducer.java +++ /dev/null @@ -1,60 +0,0 @@ -package ru.dragonestia.msb3.api.glyph.glyph.space.mojang; - -import net.kyori.adventure.key.Key; -import org.jetbrains.annotations.NotNull; -import ru.dragonestia.msb3.api.glyph.compile.ArbitraryCharacterFactory; -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.space.AbstractSpacesGlyphResourceProducer; -import team.unnamed.creative.font.FontProvider; - -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.Set; - -public class MojangSpacesGlyphResourceProducer extends AbstractSpacesGlyphResourceProducer { - - private Set fontProviders; - - public MojangSpacesGlyphResourceProducer(Key key) { - super(key); - } - - @Override - public boolean produced() { - return fontProviders != null; - } - - @Override - public void produceResources(ArbitraryCharacterFactory characterFactory) { - if (fontProviders != null) { - throw new ResourceAlreadyProducedException(); - } - - mapping = new HashMap<>(); - - var fontProviderBuilder = FontProvider.space(); - - for (int length = 1; length <= 2048; length *= 2) { - fontProviderBuilder.advance(retrieveCharacter(characterFactory, length), length); - fontProviderBuilder.advance(retrieveCharacter(characterFactory, length * (-1)), length * (-1)); - } - - this.fontProviders = Collections.singleton(fontProviderBuilder.build()); - } - - @Override - public @NotNull Collection<@NotNull FontProvider> fontProviders() throws ResourceNotProducedException { - if (fontProviders == null) { - throw new ResourceNotProducedException(); - } - return fontProviders; - } - - private Character retrieveCharacter(ArbitraryCharacterFactory characterFactory, int length) { - char character = characterFactory.nextCharacter(); - mapping.put(length, character); - return character; - } -} diff --git a/api/src/main/java/ru/dragonestia/msb3/api/glyph/pack/DefaultGlyphResourcePack.java b/api/src/main/java/ru/dragonestia/msb3/api/glyph/pack/DefaultGlyphResourcePack.java deleted file mode 100644 index 4cac26b..0000000 --- a/api/src/main/java/ru/dragonestia/msb3/api/glyph/pack/DefaultGlyphResourcePack.java +++ /dev/null @@ -1,63 +0,0 @@ -package ru.dragonestia.msb3.api.glyph.pack; - -import org.jetbrains.annotations.NotNull; - -import lombok.AccessLevel; -import lombok.NoArgsConstructor; -import ru.dragonestia.msb3.api.glyph.compile.GlyphCompiler; -import ru.dragonestia.msb3.api.glyph.compile.ResourceProducer; -import team.unnamed.creative.part.ResourcePackPart; - -import java.util.*; - -@NoArgsConstructor(access = AccessLevel.PROTECTED) -public class DefaultGlyphResourcePack implements GlyphResourcePack { - - private final Map raw = new HashMap<>(); - private final Map compiled = new HashMap<>(); - - private final Set resources = new HashSet<>(); - - @Override - public @NotNull Collection<@NotNull ResourcePackPart> all() { - if (!raw.isEmpty()) { - compileAll(); - } - return resources; - } - - @Override - public void compileAll() { - var resources = GlyphCompiler.instance().compile(raw.values()); - this.resources.addAll(resources); - compiled.putAll(raw); - raw.clear(); - } - - @Override - public @NotNull GlyphResourcePack with(@NotNull ResourceIdentifier<@NotNull T> id, @NotNull T producer) { - if (raw.containsKey(id.key()) || compiled.containsKey(id.key())) { - throw new IllegalArgumentException("Producer with this identifier already registered"); - } - raw.put(id.key(), producer); - return this; - } - - @Override - public @NotNull GlyphResourcePack with(@NotNull ResourcePackPart resource) { - resources.add(resource); - return this; - } - - @Override - public @NotNull T get(@NotNull ResourceIdentifier<@NotNull T> id) throws IllegalArgumentException { - if (!compiled.containsKey(id.key())) { - throw new IllegalArgumentException("Producer with that identifier is not compiled. Provided key: " + id.key()); - } - ResourceProducer producer = compiled.get(id.key()); - if (!id.getType().isAssignableFrom(producer.getClass())) { - throw new IllegalArgumentException("Wrong producer type"); - } - return (T) producer; - } -} diff --git a/api/src/main/java/ru/dragonestia/msb3/api/glyph/pack/GlyphResourcePack.java b/api/src/main/java/ru/dragonestia/msb3/api/glyph/pack/GlyphResourcePack.java deleted file mode 100644 index fd1877f..0000000 --- a/api/src/main/java/ru/dragonestia/msb3/api/glyph/pack/GlyphResourcePack.java +++ /dev/null @@ -1,58 +0,0 @@ -package ru.dragonestia.msb3.api.glyph.pack; - -import org.jetbrains.annotations.Contract; -import org.jetbrains.annotations.NotNull; -import ru.dragonestia.msb3.api.glyph.compile.ResourceProducer; -import ru.dragonestia.msb3.api.glyph.glyph.space.SpacesGlyphResourceProducer; -import ru.dragonestia.msb3.api.glyph.glyph.space.mojang.MojangSpacesGlyph; -import team.unnamed.creative.ResourcePack; -import team.unnamed.creative.part.ResourcePackPart; - -import java.util.Collection; - -public interface GlyphResourcePack { - - @NotNull Collection<@NotNull ResourcePackPart> all(); - - void compileAll(); - - @Contract("_, _ -> this") - @NotNull GlyphResourcePack with(@NotNull ResourceIdentifier<@NotNull T> id, @NotNull T producer); - - @Contract("_ -> this") - @NotNull GlyphResourcePack with(@NotNull ResourcePackPart resource); - - @Contract("_ -> this") - default @NotNull GlyphResourcePack with(@NotNull ResourcePackPart... resources) { - for (ResourcePackPart resource : resources) { - with(resource); - } - return this; - } - - @Contract("_ -> this") - default @NotNull GlyphResourcePack with(@NotNull Collection<@NotNull ResourcePackPart> resources) { - resources.forEach(this::with); - return this; - } - - @Contract("-> this") - default @NotNull GlyphResourcePack withMojangSpaces() { - with(ResourceIdentifier.SPACES, MojangSpacesGlyph.create()); - return this; - } - - @NotNull T get(@NotNull ResourceIdentifier<@NotNull T> id) throws IllegalArgumentException; - - default @NotNull SpacesGlyphResourceProducer spaces() { - return get(ResourceIdentifier.SPACES); - } - - default void writeAll(@NotNull ResourcePack resourcePack) { - all().forEach(resourcePack::part); - } - - static @NotNull GlyphResourcePack create() { - return new DefaultGlyphResourcePack(); - } -} diff --git a/api/src/main/java/ru/dragonestia/msb3/api/glyph/pack/ImageResourceIdentifier.java b/api/src/main/java/ru/dragonestia/msb3/api/glyph/pack/ImageResourceIdentifier.java deleted file mode 100644 index bf6a472..0000000 --- a/api/src/main/java/ru/dragonestia/msb3/api/glyph/pack/ImageResourceIdentifier.java +++ /dev/null @@ -1,12 +0,0 @@ -package ru.dragonestia.msb3.api.glyph.pack; - -import org.jetbrains.annotations.NotNull; -import ru.dragonestia.msb3.api.glyph.glyph.image.ImageGlyph; - -public interface ImageResourceIdentifier extends ResourceIdentifier<@NotNull ImageGlyph> { - - @Override - default @NotNull Class<@NotNull ImageGlyph> getType() { - return ImageGlyph.class; - } -} diff --git a/api/src/main/java/ru/dragonestia/msb3/api/glyph/pack/ResourceIdentifier.java b/api/src/main/java/ru/dragonestia/msb3/api/glyph/pack/ResourceIdentifier.java deleted file mode 100644 index 0d8885b..0000000 --- a/api/src/main/java/ru/dragonestia/msb3/api/glyph/pack/ResourceIdentifier.java +++ /dev/null @@ -1,19 +0,0 @@ -package ru.dragonestia.msb3.api.glyph.pack; - -import org.jetbrains.annotations.NotNull; -import ru.dragonestia.msb3.api.glyph.compile.ResourceProducer; -import ru.dragonestia.msb3.api.glyph.glyph.image.multicharacter.LanguageGlyphCollection; -import ru.dragonestia.msb3.api.glyph.glyph.space.SpacesGlyphResourceProducer; - -public interface ResourceIdentifier { - - @NotNull StringIdentifier<@NotNull SpacesGlyphResourceProducer> SPACES = StringIdentifier.of("spaces", SpacesGlyphResourceProducer.class); - - @NotNull StringIdentifier<@NotNull LanguageGlyphCollection> MINECRAFT_FONT = StringIdentifier.of("minecraft_font", LanguageGlyphCollection.class); - - String STRING_IDENTIFIER_NAMESPACE = "glyphs"; - - @NotNull String key(); - - @NotNull Class getType(); -} diff --git a/api/src/main/java/ru/dragonestia/msb3/api/glyph/pack/StringIdentifier.java b/api/src/main/java/ru/dragonestia/msb3/api/glyph/pack/StringIdentifier.java deleted file mode 100644 index 52e8621..0000000 --- a/api/src/main/java/ru/dragonestia/msb3/api/glyph/pack/StringIdentifier.java +++ /dev/null @@ -1,37 +0,0 @@ -package ru.dragonestia.msb3.api.glyph.pack; - -import org.jetbrains.annotations.NotNull; -import ru.dragonestia.msb3.api.glyph.compile.ResourceProducer; -import ru.dragonestia.msb3.api.glyph.glyph.image.ImageGlyph; - -public class StringIdentifier implements ResourceIdentifier { - - private final @NotNull String id; - - private final @NotNull Class type; - - protected StringIdentifier(@NotNull String id, @NotNull Class type) { - this.id = id; - this.type = type; - } - - @Override - public @NotNull Class getType() { - return type; - } - - @Override - public @NotNull String key() { - return id; - } - - public static @NotNull StringIdentifier<@NotNull T> of( - @NotNull String id, - @NotNull Class type) { - return new StringIdentifier<>(id, type); - } - - public static @NotNull StringIdentifier<@NotNull ImageGlyph> image(@NotNull String id) { - return new StringIdentifier<>(id, ImageGlyph.class); - } -} diff --git a/api/src/main/java/ru/dragonestia/msb3/api/glyph/util/ArrayUtil.java b/api/src/main/java/ru/dragonestia/msb3/api/glyph/util/ArrayUtil.java deleted file mode 100644 index f7029b5..0000000 --- a/api/src/main/java/ru/dragonestia/msb3/api/glyph/util/ArrayUtil.java +++ /dev/null @@ -1,19 +0,0 @@ -package ru.dragonestia.msb3.api.glyph.util; - -import org.jetbrains.annotations.NotNull; - -import lombok.experimental.UtilityClass; - -import java.util.List; - -@UtilityClass -public class ArrayUtil { - - public char[] toCharArray(@NotNull List list) { - char[] arr = new char[list.size()]; - for (int i = 0; i < list.size(); i++) { - arr[i] = list.get(i); - } - return arr; - } -} diff --git a/api/src/main/java/ru/dragonestia/msb3/api/glyph/util/ImageUtil.java b/api/src/main/java/ru/dragonestia/msb3/api/glyph/util/ImageUtil.java deleted file mode 100644 index 1bf4c28..0000000 --- a/api/src/main/java/ru/dragonestia/msb3/api/glyph/util/ImageUtil.java +++ /dev/null @@ -1,28 +0,0 @@ -package ru.dragonestia.msb3.api.glyph.util; - -import lombok.experimental.UtilityClass; - -import java.awt.*; -import java.awt.image.BufferedImage; - -@UtilityClass -public class ImageUtil { - - public int calculateWidth(BufferedImage image, int xFrom, int yFrom, int xTo, int yTo) { - int width; - for (width = xTo - 1; width > xFrom; width--) { - for (int height = yFrom; height < yTo; height++) { - if (new Color(image.getRGB(width, height), true) - .getAlpha() == 255) { - return width - xFrom + 1; - } - } - } - - return width - xFrom + 1; - } - - public int calculateWidth(BufferedImage image) { - return calculateWidth(image, 0, 0, image.getWidth(), image.getHeight()); - } -} diff --git a/api/src/main/java/ru/dragonestia/msb3/api/item/BlankSlotItem.java b/api/src/main/java/ru/dragonestia/msb3/api/item/BlankSlotItem.java index 8c1ada0..b71978a 100644 --- a/api/src/main/java/ru/dragonestia/msb3/api/item/BlankSlotItem.java +++ b/api/src/main/java/ru/dragonestia/msb3/api/item/BlankSlotItem.java @@ -3,8 +3,7 @@ package ru.dragonestia.msb3.api.item; import net.kyori.adventure.key.Key; import net.minestom.server.item.ItemStack; import net.minestom.server.item.Material; -import ru.dragonestia.msb3.api.ServerBootstrap; -import ru.dragonestia.msb3.api.glyph.glyph.Glyph; +import ru.dragonestia.msb3.api.boot.ServerBootstrap; import ru.dragonestia.msb3.api.util.ResourceFromJar; import team.unnamed.creative.base.Writable; import team.unnamed.creative.model.*; @@ -24,7 +23,7 @@ public class BlankSlotItem { public synchronized static ItemStack getItem() { if (stack == null) { stack = ItemStack.builder(Material.PAPER) - .customModelData(1) + .itemModel("minecraft:air") .build(); } @@ -32,7 +31,7 @@ public class BlankSlotItem { } public static Collection compile() { - var modelKey = Key.key(Glyph.DEFAULT_NAMESPACE, "blank_slot"); + var modelKey = Key.key("msb3", "blank_slot"); var itemKey = Key.key("item/paper"); Model blankSlotModel = Model.model() @@ -52,7 +51,7 @@ public class BlankSlotItem { .overrides(ItemOverride.of(modelKey, ItemPredicate.customModelData(1))) .build(); - Texture texture = Texture.texture(Key.key(Glyph.DEFAULT_NAMESPACE, "blank_slot.png"), BLANK_SLOT_IMAGE_WRITABLE); + Texture texture = Texture.texture(Key.key("msb3", "blank_slot.png"), BLANK_SLOT_IMAGE_WRITABLE); return Arrays.asList(blankSlotModel, paperItemModel, texture); } } diff --git a/api/src/main/java/ru/dragonestia/msb3/api/item/ItemPrefabManager.java b/api/src/main/java/ru/dragonestia/msb3/api/item/ItemPrefabManager.java index 575f2b3..4a5bb7a 100644 --- a/api/src/main/java/ru/dragonestia/msb3/api/item/ItemPrefabManager.java +++ b/api/src/main/java/ru/dragonestia/msb3/api/item/ItemPrefabManager.java @@ -1,7 +1,8 @@ package ru.dragonestia.msb3.api.item; +import lombok.experimental.UtilityClass; import lombok.extern.log4j.Log4j2; -import ru.dragonestia.msb3.api.ServerBootstrap; +import ru.dragonestia.msb3.api.boot.ServerBootstrap; import ru.dragonestia.msb3.api.item.prefab.ItemPrefab; import java.util.HashMap; @@ -9,12 +10,11 @@ import java.util.Map; import java.util.Optional; @Log4j2 +@UtilityClass public final class ItemPrefabManager { private final Map prefabs = new HashMap<>(); - public ItemPrefabManager() {} - public synchronized void register(ItemPrefab prefab) { if (ServerBootstrap.isStarted()) { throw new IllegalStateException("Server is already started"); diff --git a/api/src/main/java/ru/dragonestia/msb3/api/item/prefab/ItemPrefab.java b/api/src/main/java/ru/dragonestia/msb3/api/item/prefab/ItemPrefab.java index 465537d..3ade5f1 100644 --- a/api/src/main/java/ru/dragonestia/msb3/api/item/prefab/ItemPrefab.java +++ b/api/src/main/java/ru/dragonestia/msb3/api/item/prefab/ItemPrefab.java @@ -3,7 +3,7 @@ package ru.dragonestia.msb3.api.item.prefab; import net.kyori.adventure.nbt.BinaryTag; import net.minestom.server.item.ItemStack; import net.minestom.server.tag.Tag; -import ru.dragonestia.msb3.api.ServerBootstrap; +import ru.dragonestia.msb3.api.item.ItemPrefabManager; import java.util.Optional; import java.util.function.Supplier; @@ -22,12 +22,12 @@ public class ItemPrefab { } public static Optional of(String identifier) { - return ServerBootstrap.getInstance().getItemPrefabManager().getPrefab(identifier); + return ItemPrefabManager.getPrefab(identifier); } public static Optional of(ItemStack item) { if (item == null || item.hasTag(TAG_IDENTIFIER)) return Optional.empty(); - return ServerBootstrap.getInstance().getItemPrefabManager().getPrefab(item.getTag(TAG_IDENTIFIER)); + return ItemPrefabManager.getPrefab(item.getTag(TAG_IDENTIFIER)); } public final String getIdentifier() { diff --git a/api/src/main/java/ru/dragonestia/msb3/api/module/FlatWorldModule.java b/api/src/main/java/ru/dragonestia/msb3/api/module/FlatWorldModule.java index 1decc6b..1abedcf 100644 --- a/api/src/main/java/ru/dragonestia/msb3/api/module/FlatWorldModule.java +++ b/api/src/main/java/ru/dragonestia/msb3/api/module/FlatWorldModule.java @@ -1,17 +1,16 @@ package ru.dragonestia.msb3.api.module; +import lombok.Getter; import net.minestom.server.MinecraftServer; import net.minestom.server.coordinate.Pos; import net.minestom.server.entity.GameMode; import net.minestom.server.event.player.AsyncPlayerConfigurationEvent; -import net.minestom.server.instance.Chunk; -import net.minestom.server.instance.DynamicChunk; -import net.minestom.server.instance.IChunkLoader; -import net.minestom.server.instance.Instance; +import net.minestom.server.instance.*; import net.minestom.server.instance.block.Block; import net.minestom.server.world.DimensionType; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import ru.dragonestia.msb3.api.world.World; import ru.dragonestia.msb3.api.world.WorldFactory; import java.util.concurrent.CompletableFuture; @@ -19,51 +18,53 @@ import java.util.concurrent.CompletableFuture; public class FlatWorldModule { private static boolean used = false; + @Getter private static World world; private FlatWorldModule() {} - public static synchronized void init(GameMode gameMode) { - if (used) return; - used = true; - + public static void init(GameMode gameMode) { var dimension = MinecraftServer.getDimensionTypeRegistry().register("msb3:full_bright", DimensionType.builder() .ambientLight(2f) .build()); var factory = WorldFactory.custom(dimension, new IChunkLoader() { @Override - public @NotNull CompletableFuture<@Nullable Chunk> loadChunk(@NotNull Instance instance, int chunkX, int chunkZ) { - return CompletableFuture.supplyAsync(() -> { - var chunk = new DynamicChunk(instance, chunkX, chunkZ); - for (int x = 0; x < 16; x++) { - for (int z = 0; z < 16; z++) { - for (int y = 0; y <= 10; y++) { - var block = switch (y) { - case 10 -> Block.GRASS_BLOCK; - case 9, 8, 7 -> Block.DIRT; - case 0 -> Block.BEDROCK; - default -> Block.STONE; - }; - chunk.setBlock(x, y, z, block); - } + public @NotNull Chunk loadChunk(@NotNull Instance instance, int chunkX, int chunkZ) { + + var chunk = new DynamicChunk(instance, chunkX, chunkZ); + for (int x = 0; x < 16; x++) { + for (int z = 0; z < 16; z++) { + for (int y = 0; y <= 10; y++) { + var block = switch (y) { + case 10 -> Block.GRASS_BLOCK; + case 9, 8, 7 -> Block.DIRT; + case 0 -> Block.BEDROCK; + default -> Block.STONE; + }; + chunk.setBlock(x, y, z, block); } } - return chunk; - }); + } + return chunk; } @Override - public @NotNull CompletableFuture saveChunk(@NotNull Chunk chunk) { - return new CompletableFuture<>(); - } + public void saveChunk(@NotNull Chunk chunk) {} }); - var world = factory.createWorldSync(); + init(gameMode, factory.createWorldSync(), new Pos(0, 11, 0)); + } + + public static synchronized void init(GameMode gameMode, World world, Pos spawnPos) { + if (used) return; + used = true; + + FlatWorldModule.world = world; MinecraftServer.getGlobalEventHandler().addListener(AsyncPlayerConfigurationEvent.class, event -> { var player = event.getPlayer(); - player.setRespawnPoint(new Pos(0, 11, 0)); + player.setRespawnPoint(spawnPos); player.setGameMode(gameMode); event.setSpawningInstance(world.getInstance()); diff --git a/api/src/main/java/ru/dragonestia/msb3/api/module/ResourcePackRepositoryModule.java b/api/src/main/java/ru/dragonestia/msb3/api/module/ResourcePackRepositoryModule.java index 43559c5..e89b3d2 100644 --- a/api/src/main/java/ru/dragonestia/msb3/api/module/ResourcePackRepositoryModule.java +++ b/api/src/main/java/ru/dragonestia/msb3/api/module/ResourcePackRepositoryModule.java @@ -1,13 +1,13 @@ package ru.dragonestia.msb3.api.module; import lombok.SneakyThrows; +import lombok.experimental.UtilityClass; import net.kyori.adventure.resource.ResourcePackCallback; import net.kyori.adventure.resource.ResourcePackInfo; import net.kyori.adventure.resource.ResourcePackRequest; import net.kyori.adventure.text.Component; import net.minestom.server.MinecraftServer; import net.minestom.server.event.player.PlayerSpawnEvent; -import ru.dragonestia.msb3.api.ServerBootstrap; import team.unnamed.creative.BuiltResourcePack; import team.unnamed.creative.base.Writable; import team.unnamed.creative.server.ResourcePackServer; @@ -22,19 +22,16 @@ import java.util.UUID; import static net.kyori.adventure.text.format.NamedTextColor.RED; +@UtilityClass public class ResourcePackRepositoryModule { - private static boolean used = false; - - private ResourcePackRepositoryModule() {} + private boolean used = false; @SneakyThrows - public static synchronized void init(ServerBootstrap bootstrap, String address, int port) { + public synchronized void init(String address, int port) { if (used) return; used = true; - bootstrap.getResourcePackManager().compile(); - var file = new File("./resource-pack.zip"); var hash = calculateHash(file); var uuid = UUID.randomUUID(); @@ -46,6 +43,8 @@ public class ResourcePackRepositoryModule { server.start(); MinecraftServer.getGlobalEventHandler().addListener(PlayerSpawnEvent.class, event -> { + if (!event.isFirstSpawn()) return; + var player = event.getPlayer(); player.sendResourcePacks(ResourcePackRequest.resourcePackRequest() @@ -59,7 +58,7 @@ public class ResourcePackRepositoryModule { } @SneakyThrows - private static String calculateHash(File file) { + private String calculateHash(File file) { MessageDigest digest = MessageDigest.getInstance("SHA-1"); try (InputStream fis = new FileInputStream(file)) { @@ -68,7 +67,7 @@ public class ResourcePackRepositoryModule { } } - private static String byteArray2Hex(final byte[] hash) { + private String byteArray2Hex(final byte[] hash) { Formatter formatter = new Formatter(); for (byte b: hash) formatter.format("%02x", b); return formatter.toString(); diff --git a/api/src/main/java/ru/dragonestia/msb3/api/player/MsbPlayer.java b/api/src/main/java/ru/dragonestia/msb3/api/player/MsbPlayer.java new file mode 100644 index 0000000..50b6c62 --- /dev/null +++ b/api/src/main/java/ru/dragonestia/msb3/api/player/MsbPlayer.java @@ -0,0 +1,58 @@ +package ru.dragonestia.msb3.api.player; + +import lombok.Getter; +import net.minestom.server.entity.Player; +import net.minestom.server.network.player.GameProfile; +import net.minestom.server.network.player.PlayerConnection; +import org.jetbrains.annotations.NotNull; +import ru.dragonestia.msb3.api.util.UncheckedRunnable; + +import java.time.Instant; +import java.util.LinkedHashMap; +import java.util.Map; + +public class MsbPlayer extends Player { + + private final Map contexts = new LinkedHashMap<>(); + @Getter private final Instant startSessionTime = Instant.now(); + + public MsbPlayer(@NotNull PlayerConnection playerConnection, @NotNull GameProfile gameProfile) { + super(playerConnection, gameProfile); + } + + void putContext(PlayerContext ctx) { + contexts.put(ctx.getClass().getName(), ctx); + } + + @SuppressWarnings("unchecked") + public T getContext(Class clazz) { + return (T) contexts.get(clazz.getName()); + } + + public void initContexts() { + for (var entry: contexts.entrySet()) { + var ctxKey = entry.getKey(); + var ctx = entry.getValue(); + + UncheckedRunnable.runIgnoreException(ctx::init); + } + } + + public void emitSpawnSignalForContexts() { + for (var entry: contexts.entrySet()) { + var ctxKey = entry.getKey(); + var ctx = entry.getValue(); + + UncheckedRunnable.runIgnoreException(ctx::firstSpawn); + } + } + + public void disposeContexts() { + for (var entry: contexts.entrySet()) { + var ctxKey = entry.getKey(); + var ctx = entry.getValue(); + + UncheckedRunnable.runIgnoreException(ctx::dispose); + } + } +} diff --git a/api/src/main/java/ru/dragonestia/msb3/api/player/PlayerContext.java b/api/src/main/java/ru/dragonestia/msb3/api/player/PlayerContext.java new file mode 100644 index 0000000..c0e882a --- /dev/null +++ b/api/src/main/java/ru/dragonestia/msb3/api/player/PlayerContext.java @@ -0,0 +1,24 @@ +package ru.dragonestia.msb3.api.player; + +import lombok.Getter; +import net.minestom.server.entity.Player; + +@Getter +public abstract class PlayerContext { + + private final MsbPlayer player; + + public PlayerContext(MsbPlayer player) { + this.player = player; + } + + public abstract void init(); + + public void firstSpawn() {} + + public abstract void dispose(); + + public static T of(Player player, Class clazz) { + return ((MsbPlayer) player).getContext(clazz); + } +} diff --git a/api/src/main/java/ru/dragonestia/msb3/api/player/PlayerContextManager.java b/api/src/main/java/ru/dragonestia/msb3/api/player/PlayerContextManager.java new file mode 100644 index 0000000..ba0a571 --- /dev/null +++ b/api/src/main/java/ru/dragonestia/msb3/api/player/PlayerContextManager.java @@ -0,0 +1,45 @@ +package ru.dragonestia.msb3.api.player; + +import lombok.experimental.UtilityClass; +import lombok.extern.log4j.Log4j2; +import net.minestom.server.entity.Player; +import net.minestom.server.network.player.GameProfile; +import net.minestom.server.network.player.PlayerConnection; +import org.jetbrains.annotations.NotNull; + +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.function.Function; + +@Log4j2 +@UtilityClass +public class PlayerContextManager { + + private final Map> contexts = new LinkedHashMap<>(); + + public MsbPlayer create(@NotNull PlayerConnection playerConnection, @NotNull GameProfile gameProfile) { + var player = new MsbPlayer(playerConnection, gameProfile); + + for (var entry: contexts.entrySet()) { + var ctxKey = entry.getKey(); + var ctx = entry.getValue().apply(player); + + player.putContext(ctx); + } + + return player; + } + + public void registerContext(Class clazz, Function ctxProvider) { + var key = clazz.getName(); + var prev = contexts.put(key, ctxProvider); + + if (prev != null) { + log.warn("Duplicate context registration for {}", key); + } + } + + public T getContext(Player player, Class clazz) { + return ((MsbPlayer) player).getContext(clazz); + } +} diff --git a/api/src/main/java/ru/dragonestia/msb3/api/player/defaults/KeyedBossBarContext.java b/api/src/main/java/ru/dragonestia/msb3/api/player/defaults/KeyedBossBarContext.java new file mode 100644 index 0000000..6e4223c --- /dev/null +++ b/api/src/main/java/ru/dragonestia/msb3/api/player/defaults/KeyedBossBarContext.java @@ -0,0 +1,45 @@ +package ru.dragonestia.msb3.api.player.defaults; + +import net.kyori.adventure.bossbar.BossBar; +import ru.dragonestia.msb3.api.player.MsbPlayer; +import ru.dragonestia.msb3.api.player.PlayerContext; + +import java.util.HashMap; +import java.util.Map; + +public class KeyedBossBarContext extends PlayerContext { + + private final Map bossBars = new HashMap<>(); + + public KeyedBossBarContext(MsbPlayer player) { + super(player); + } + + @Override + public void init() { + + } + + @Override + public void dispose() { + hideAll(); + } + + public void hideAll() { + bossBars.forEach((barId, bossBar) -> getPlayer().hideBossBar(bossBar)); + } + + public void hide(String bossBarId) { + var bossBar = bossBars.remove(bossBarId); + if (bossBar == null) return; + + getPlayer().hideBossBar(bossBar); + } + + public BossBar show(String bossBarId, BossBar bossBar) { + var prev = bossBars.put(bossBarId, bossBar); + if (prev != null) getPlayer().hideBossBar(prev); + getPlayer().showBossBar(bossBar); + return bossBar; + } +} diff --git a/api/src/main/java/ru/dragonestia/msb3/api/player/defaults/NavigatorContext.java b/api/src/main/java/ru/dragonestia/msb3/api/player/defaults/NavigatorContext.java new file mode 100644 index 0000000..0193181 --- /dev/null +++ b/api/src/main/java/ru/dragonestia/msb3/api/player/defaults/NavigatorContext.java @@ -0,0 +1,144 @@ +package ru.dragonestia.msb3.api.player.defaults; + +import lombok.Setter; +import net.kyori.adventure.bossbar.BossBar; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.TextColor; +import net.minestom.server.coordinate.Point; +import net.minestom.server.coordinate.Vec; +import net.minestom.server.event.player.PlayerMoveEvent; +import ru.dragonestia.msb3.api.player.MsbPlayer; +import ru.dragonestia.msb3.api.player.PlayerContext; +import ru.dragonestia.msb3.api.ui.bossbar.KeyedBossBars; +import ru.dragonestia.msb3.api.ui.navigator.Navigator; +import ru.dragonestia.msb3.api.ui.navigator.Navigators; +import ru.dragonestia.msb3.resource.glyph.GlyphComponentBuilder; +import ru.dragonestia.msb3.resource.glyph.GlyphImage; +import ru.dragonestia.msb3.resource.glyph.MinecraftFont; + +public class NavigatorContext extends PlayerContext implements Navigator { + + private final static Component EMPTY = Component.text(" "); + + private BossBar bossBar; + private Point target; + private String text; + private MinecraftFont.Text cachedText; + private boolean prevTargetEmpty = true; + private boolean enabled = true; + @Setter private String format = "[ %s ]"; + @Setter private TextColor textColor = TextColor.color(0xF8FF91); + @Setter private GlyphImage arrowBackground = Navigators.GLYPH_BACKGROUND; + + public NavigatorContext(MsbPlayer player) { + super(player); + } + + @Override + public void init() {} + + @Override + public void firstSpawn() { + getPlayer().eventNode().addListener(PlayerMoveEvent.class, event -> update()); + + bossBar = KeyedBossBars.showText(getPlayer(), "msb3_navigator", EMPTY); + } + + @Override + public void dispose() { + target = null; + text = null; + } + + @Override + public void setEnabled(boolean enabled) { + this.enabled = enabled; + update(); + } + + @Override + public boolean isEnabled() { + return enabled; + } + + @Override + public void setTarget(Point point) { + setTarget(point, null); + } + + @Override + public synchronized void setTarget(Point target, String text) { + prevTargetEmpty = this.target == null; + this.target = target; + this.text = text; + + update(); + } + + @Override + public void removeTarget() { + setTarget(null); + } + + private void update() { + if (!enabled || (prevTargetEmpty && target == null)) return; + + if (bossBar != null) bossBar.name(render()); + } + + private Component render() { + if (target == null) { + return EMPTY; + } + + var builder = new GlyphComponentBuilder(); + + builder.append((int) (arrowBackground.width() / -2.15) + 1, arrowBackground); + + // Text + if (text != null) { + builder.setColorTo(textColor); + if (cachedText == null) { + cachedText = MinecraftFont.translateByLineNumber(0, format.formatted(text)); + } + builder.append((int) (cachedText.width() / -2.15), cachedText); + builder.resetColor(); + } + + // Arrow + builder.append(0, getArrowGlyph()); + + return builder.build(); + } + + private GlyphImage getArrowGlyph() { + var playerPos = getPlayer().getPosition(); + var angle = computeAngle(playerPos.asVec(), playerPos.direction(), Vec.fromPoint(target)); + + if (!(angle < -2.749) && !(angle >= 2.749)) { + if (angle < -1.963) return Navigators.GLYPH_DOWN_RIGHT; + if (angle < -1.178) return Navigators.GLYPH_RIGHT; + if (angle < -0.393) return Navigators.GLYPH_UP_RIGHT; + if (angle < 0.393) return Navigators.GLYPH_UP; + if (angle < 1.178) return Navigators.GLYPH_UP_LEFT; + if (angle < 1.963) return Navigators.GLYPH_LEFT; + if (angle < 2.749) return Navigators.GLYPH_DOWN_LEFT; + } + return Navigators.GLYPH_DOWN; + } + + private double computeAngle(Vec start, Vec direction, Vec destination) { + Vec destinationDirection = destination.sub(start); + double playerAngle = Math.atan2(direction.z(), direction.x()); + double locAngle = Math.atan2(destinationDirection.z(), destinationDirection.x()); + + double angle; + for (angle = playerAngle - locAngle; angle > Math.PI; angle -= (Math.PI * 2D)) {} + + while (angle < -Math.PI) { + angle += (Math.PI * 2D); + } + + return angle; + } +} diff --git a/api/src/main/java/ru/dragonestia/msb3/api/player/defaults/TalksContext.java b/api/src/main/java/ru/dragonestia/msb3/api/player/defaults/TalksContext.java new file mode 100644 index 0000000..4d910d2 --- /dev/null +++ b/api/src/main/java/ru/dragonestia/msb3/api/player/defaults/TalksContext.java @@ -0,0 +1,40 @@ +package ru.dragonestia.msb3.api.player.defaults; + +import lombok.Getter; +import lombok.Setter; +import ru.dragonestia.msb3.api.dialog.DialogRegistry; +import ru.dragonestia.msb3.api.dialog.data.TalksPlayerData; +import ru.dragonestia.msb3.api.player.MsbPlayer; +import ru.dragonestia.msb3.api.player.PlayerContext; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +@Getter +public class TalksContext extends PlayerContext { + + @Setter private boolean ignoreDialogConditions = false; + private final Set openedDialogues = Collections.synchronizedSet(new HashSet<>()); + private final Set clickedButtons = Collections.synchronizedSet(new HashSet<>()); + + public TalksContext(MsbPlayer player) { + super(player); + } + + @Override + public void init() { + var data = DialogRegistry.getPlayerDataProvider().load(getPlayer()); + + openedDialogues.addAll(data.openedDialogs()); + clickedButtons.addAll(data.clickedButtons()); + } + + @Override + public void dispose() { + DialogRegistry.getPlayerDataProvider().save(getPlayer(), new TalksPlayerData( + openedDialogues, + clickedButtons + )); + } +} diff --git a/api/src/main/java/ru/dragonestia/msb3/api/resource/DialogueResources.java b/api/src/main/java/ru/dragonestia/msb3/api/resource/DialogueResources.java new file mode 100644 index 0000000..b150c62 --- /dev/null +++ b/api/src/main/java/ru/dragonestia/msb3/api/resource/DialogueResources.java @@ -0,0 +1,272 @@ +package ru.dragonestia.msb3.api.resource; + +import lombok.experimental.UtilityClass; +import net.kyori.adventure.key.Key; +import ru.dragonestia.msb3.api.resource.dialog.*; +import ru.dragonestia.msb3.resource.Resources; +import ru.dragonestia.msb3.resource.glyph.GlyphImage; +import ru.dragonestia.msb3.resource.utils.ImageUtils; +import team.unnamed.creative.base.Writable; + +import javax.imageio.ImageIO; +import java.awt.image.BufferedImage; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +@UtilityClass +public class DialogueResources { + + public static final String DEFAULT = "default"; + + private final Map avatars = new HashMap<>(); + private final Map avatarFrames = new HashMap<>(); + private final Map scrollUp = new HashMap<>(); + private final Map scrollDown = new HashMap<>(); + private final Map backgrounds = new HashMap<>(); + private final Map substrates = new HashMap<>(); + private final Map buttons = new HashMap<>(); + private final Map activeFields = new HashMap<>(); + private final Map notActiveFields = new HashMap<>(); + + public void registerAvatar(String identifier, Writable writable) { + avatars.put(identifier, Resources.createGlyph( + Key.key("msb3", "dialogue/avatar/" + identifier), + writable, + GlyphPositions.avatar.height(), + GlyphPositions.avatar.y() + )); + } + + public void registerAvatarFrame(String identifier, Writable writable) { + avatarFrames.put(identifier, Resources.createGlyph( + Key.key("msb3", "dialogue/avatar_frame/" + identifier), + writable, + GlyphPositions.avatar.frameHeight(), + GlyphPositions.avatar.y() + GlyphPositions.avatar.frameBorderSize() + )); + } + + public void registerScrollUp(String identifier, Writable writable) { + scrollUp.put(identifier, Resources.createGlyph( + Key.key("msb3", "dialogue/scroll_up/" + identifier), + writable, + GlyphPositions.scrollPhraseButton.height(), + GlyphPositions.scrollPhraseButton.buttonY() + )); + } + + public void registerScrollDown(String identifier, Writable writable) { + scrollDown.put(identifier, Resources.createGlyph( + Key.key("msb3", "dialogue/scroll_down/" + identifier), + writable, + GlyphPositions.scrollPhraseButton.height(), + GlyphPositions.scrollPhraseButton.buttonY() + )); + } + + public void registerBackground(String identifier, Writable writable) { + BufferedImage image; + Writable part1; + Writable part2; + Writable part3; + Writable part4; + + try (var steam = new ByteArrayInputStream(writable.toByteArray())) { + image = ImageIO.read(steam); + var w = image.getWidth(); + var h = image.getHeight(); + + part1 = ImageUtils.imageToWritable(image.getSubimage(0, 0, w / 2, h / 2)); + part2 = ImageUtils.imageToWritable(image.getSubimage(w / 2, 0, w / 2, h / 2)); + part3 = ImageUtils.imageToWritable(image.getSubimage(0, h / 2, w / 2, h / 2)); + part4 = ImageUtils.imageToWritable(image.getSubimage(w / 2, h / 2, w / 2, h / 2)); + } catch (IOException ex) { + throw new RuntimeException(ex); + } + + var glyph1 = Resources.createGlyph( + Key.key("msb3", "dialogue/background/" + identifier + "__1"), + part1, + GlyphPositions.guiBackground.height() / 2, + GlyphPositions.guiBackground.topPartsY() + ); + var glyph2 = Resources.createGlyph( + Key.key("msb3", "dialogue/background/" + identifier + "__2"), + part2, + GlyphPositions.guiBackground.height() / 2, + GlyphPositions.guiBackground.topPartsY() + ); + var glyph3 = Resources.createGlyph( + Key.key("msb3", "dialogue/background/" + identifier + "__3"), + part3, + GlyphPositions.guiBackground.height() / 2, + GlyphPositions.guiBackground.bottomPartsY() + ); + var glyph4 = Resources.createGlyph( + Key.key("msb3", "dialogue/background/" + identifier + "__4"), + part4, + GlyphPositions.guiBackground.height() / 2, + GlyphPositions.guiBackground.bottomPartsY() + ); + + backgrounds.put(identifier, new Background(glyph1, glyph2, glyph3, glyph4)); + } + + public void registerSubstrate(String identifier, Writable writable) { + BufferedImage image; + Writable part1; + Writable part2; + + try (var steam = new ByteArrayInputStream(writable.toByteArray())) { + image = ImageIO.read(steam); + var w = image.getWidth(); + var h = image.getHeight(); + + part1 = ImageUtils.imageToWritable(image.getSubimage(0, 0, w / 2, h)); + part2 = ImageUtils.imageToWritable(image.getSubimage(w / 2, 0, w / 2, h)); + } catch (IOException ex) { + throw new RuntimeException(ex); + } + + var glyph1 = Resources.createGlyph( + Key.key("msb3", "dialogue/substrate/" + identifier + "__1"), + part1, + GlyphPositions.phraseSubstrate.height(), + GlyphPositions.phraseSubstrate.y() + ); + var glyph2 = Resources.createGlyph( + Key.key("msb3", "dialogue/substrate/" + identifier + "__2"), + part2, + GlyphPositions.phraseSubstrate.height(), + GlyphPositions.phraseSubstrate.y() + ); + + substrates.put(identifier, new Substrate(glyph1, glyph2)); + } + + public void registerButton(String identifier, Writable writable) { + var glyph1 = Resources.createGlyph( + Key.key("msb3", "dialogue/button/" + identifier), + writable, + GlyphPositions.answerButton.height(), + GlyphPositions.answerButton.topButtonY() + ); + var glyph2 = Resources.createGlyph( + Key.key("msb3", "dialogue/button/" + identifier), + writable, + GlyphPositions.answerButton.height(), + GlyphPositions.answerButton.topButtonY() + ); + var glyph3 = Resources.createGlyph( + Key.key("msb3", "dialogue/button/" + identifier), + writable, + GlyphPositions.answerButton.height(), + GlyphPositions.answerButton.bottomButtonY() + ); + var glyph4 = Resources.createGlyph( + Key.key("msb3", "dialogue/button/" + identifier), + writable, + GlyphPositions.answerButton.height(), + GlyphPositions.answerButton.bottomButtonY() + ); + + buttons.put(identifier, new Button(glyph1, glyph2, glyph3, glyph4)); + } + + public void registerActiveTextField(String identifier, Writable writable) { + var glyph1 = Resources.createGlyph( + Key.key("msb3", "dialogue/text_field/" + identifier), + writable, + GlyphPositions.answerField.height(), + GlyphPositions.answerField.topFieldY() + ); + var glyph2 = Resources.createGlyph( + Key.key("msb3", "dialogue/text_field/" + identifier), + writable, + GlyphPositions.answerField.height(), + GlyphPositions.answerField.topFieldY() + ); + var glyph3 = Resources.createGlyph( + Key.key("msb3", "dialogue/text_field/" + identifier), + writable, + GlyphPositions.answerField.height(), + GlyphPositions.answerField.bottomFieldY() + ); + var glyph4 = Resources.createGlyph( + Key.key("msb3", "dialogue/text_field/" + identifier), + writable, + GlyphPositions.answerField.height(), + GlyphPositions.answerField.bottomFieldY() + ); + + activeFields.put(identifier, new TextField(glyph1, glyph2, glyph3, glyph4)); + } + + public void registerNotActiveTextField(String identifier, Writable writable) { + var glyph1 = Resources.createGlyph( + Key.key("msb3", "dialogue/inactive_text_field/" + identifier), + writable, + GlyphPositions.answerField.height(), + GlyphPositions.answerField.topFieldY() + ); + var glyph2 = Resources.createGlyph( + Key.key("msb3", "dialogue/inactive_text_field/" + identifier), + writable, + GlyphPositions.answerField.height(), + GlyphPositions.answerField.topFieldY() + ); + var glyph3 = Resources.createGlyph( + Key.key("msb3", "dialogue/inactive_text_field/" + identifier), + writable, + GlyphPositions.answerField.height(), + GlyphPositions.answerField.bottomFieldY() + ); + var glyph4 = Resources.createGlyph( + Key.key("msb3", "dialogue/inactive_text_field/" + identifier), + writable, + GlyphPositions.answerField.height(), + GlyphPositions.answerField.bottomFieldY() + ); + + notActiveFields.put(identifier, new TextField(glyph1, glyph2, glyph3, glyph4)); + } + + public Optional getAvatar(String identifier) { + return Optional.ofNullable(avatars.get(identifier)); + } + + public Optional getAvatarFrame(String identifier) { + return Optional.ofNullable(avatarFrames.get(identifier)); + } + + public Optional getScrollUp(String identifier) { + return Optional.ofNullable(scrollUp.get(identifier)); + } + + public Optional getScrollDown(String identifier) { + return Optional.ofNullable(scrollDown.get(identifier)); + } + + public Optional getBackground(String identifier) { + return Optional.ofNullable(backgrounds.get(identifier)); + } + + public Optional getSubstrate(String identifier) { + return Optional.ofNullable(substrates.get(identifier)); + } + + public Optional