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 index 6939a0a..76b49ee 100644 --- a/api/src/main/java/ru/dragonestia/msb3/api/boot/DefaultBootstrap.java +++ b/api/src/main/java/ru/dragonestia/msb3/api/boot/DefaultBootstrap.java @@ -1,9 +1,14 @@ package ru.dragonestia.msb3.api.boot; import lombok.extern.log4j.Log4j2; +import net.kyori.adventure.key.Key; import net.minestom.server.MinecraftServer; import net.minestom.server.entity.GameMode; import net.minestom.server.event.player.PlayerChatEvent; +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 ru.dragonestia.msb3.api.module.FlatWorldModule; import ru.dragonestia.msb3.api.module.MotdModule; import ru.dragonestia.msb3.api.module.PrometheusMetricsModule; @@ -14,6 +19,8 @@ import ru.dragonestia.msb3.api.ui.dialogue.DialogueTheme; import team.unnamed.creative.ResourcePack; import java.net.InetSocketAddress; +import java.util.ArrayList; +import java.util.HashMap; @Log4j2 public class DefaultBootstrap extends ServerInitializer { @@ -30,9 +37,10 @@ public class DefaultBootstrap extends ServerInitializer { MinecraftServer.getGlobalEventHandler().addListener(PlayerChatEvent.class, event -> { var player = event.getPlayer(); + var dialog = new Dialog(); - var render = new DialogueRenderer(player, DialogueTheme.builder().build()); - render.setText(""" + dialog.setId(Key.key("msb3", "test_dialog")); + dialog.setText(""" Абсолютно точно. Я знаю точнo - невозможное возможно Сойти с ума, влюбиться так неосторoжно @@ -64,13 +72,15 @@ public class DefaultBootstrap extends ServerInitializer { Всё невозможное - возможно, знаю точно! На-на-на-на (на-на-на-на), а-а, а-а На-на-на-на (на-на-на-на), а-а, а-а"""); + dialog.setRememberId(true); - render.setButton(ButtonNumber.BUTTON_1, "Всем привет!", ctx -> {}); - render.setButton(ButtonNumber.BUTTON_2, "I am a teapot", ctx -> {}); - render.setButton(ButtonNumber.BUTTON_3, "I love pizza\nMamma mia\nPeperoni\nPapa carlo\nZaebumba\nZaebumba", ctx -> {}); - render.setButton(ButtonNumber.BUTTON_4, "msb3 is top!", ctx -> {}); + var buttons = new ArrayList(); + buttons.add(new DialogButton(null, "Какая-то кнопочка", null, new HashMap<>(), new ArrayList<>())); + buttons.add(new DialogButton(Key.key("msb3", "test_button"), "2 Какая-то кнопочка", "aboba", new HashMap<>(), new ArrayList<>())); + buttons.add(new DialogButton(null, "Третья", "close", new HashMap<>(), new ArrayList<>())); + dialog.setButtons(buttons); - render.show(); + dialog.open(player); }); } 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 index 789462d..de92c1f 100644 --- a/api/src/main/java/ru/dragonestia/msb3/api/boot/ServerBootstrap.java +++ b/api/src/main/java/ru/dragonestia/msb3/api/boot/ServerBootstrap.java @@ -8,6 +8,10 @@ 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.dialog.DialogRegistry; +import ru.dragonestia.msb3.api.dialog.action.CloseDialogActionHandler; +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.item.ItemUtil; import ru.dragonestia.msb3.api.player.MsbPlayer; @@ -89,6 +93,8 @@ public final class ServerBootstrap { initPlayerContextManager(); initDefaultSkins(); + + initDefaultDialogActionsAndConditions(); } private void initDefaultModules() { @@ -188,4 +194,11 @@ public final class ServerBootstrap { private void initDefaultSkins() { SkinStorage.loadSkin(SkinStorage.DEFAULT, ResourceFromJar.of("skins/default.msb3skin")); } + + private void initDefaultDialogActionsAndConditions() { + DialogRegistry.registerActionHandler("close", new CloseDialogActionHandler()); + + DialogRegistry.registerConditionHandler("always", new AlwaysDialogConditionHandler()); + DialogRegistry.registerConditionHandler("never", new NeverDialogConditionHandler()); + } } 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..c485db6 --- /dev/null +++ b/api/src/main/java/ru/dragonestia/msb3/api/dialog/Dialog.java @@ -0,0 +1,76 @@ +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.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.HashMap; +import java.util.List; + +@Log4j2 +@Getter +@Setter +@AllArgsConstructor +@NoArgsConstructor +public class Dialog { + + private final static DialogueTheme DEFAULT_THEME = DialogueTheme.builder().build(); + + private Key id; + private String text; + private boolean rememberId; + private String themeId; + private List buttons; + + public void switchDialog(Player player, DialogueRenderer renderer) { + var ctx = PlayerContext.of(player, TalksContext.class); + if (rememberId) { + ctx.getOpenedDialogues().add(id.asString()); + DebugMessage.send(player, "Идентификатор диалога %s был сохранен в список просмотренных диалогов".formatted(id.asString())); + } + + DialogueTheme theme = DEFAULT_THEME; + 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); + + 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(), this, button, buttonCtx.buttonNumber(), buttonCtx.renderer()); + button.onClick(click); + }); + } + } + + public void open(Player player) { + var renderer = new DialogueRenderer(player, DEFAULT_THEME); + switchDialog(player, renderer); + 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..f13683c --- /dev/null +++ b/api/src/main/java/ru/dragonestia/msb3/api/dialog/DialogButton.java @@ -0,0 +1,89 @@ +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) { + 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 { + action.get().handle(click, params); + DebugMessage.send(player, "Выполнение действия диалога actionId=%s params=%s".formatted(actionId, 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..fee5f29 --- /dev/null +++ b/api/src/main/java/ru/dragonestia/msb3/api/dialog/DialogButtonClick.java @@ -0,0 +1,13 @@ +package ru.dragonestia.msb3.api.dialog; + +import net.minestom.server.entity.Player; +import ru.dragonestia.msb3.api.resource.dialog.ButtonNumber; +import ru.dragonestia.msb3.api.ui.dialogue.DialogueRenderer; + +public record DialogButtonClick( + Player player, + 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..d9e53af --- /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, 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..49a25ca --- /dev/null +++ b/api/src/main/java/ru/dragonestia/msb3/api/dialog/DialogRegistry.java @@ -0,0 +1,73 @@ +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 net.minestom.server.entity.Player; +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 ru.dragonestia.msb3.api.util.DebugMessage; + +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 findDialogAndSend(Player player, Key key) { + var opt = findDialog(key); + if (opt.isEmpty()) { + log.error("Dialog {} not found", key.asString()); + DebugMessage.sendError(player, "Диалог с идентификатором %s не найден".formatted(key.asString())); + return; + } + + opt.get().open(player); + } + + 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/condition/AlwaysDialogConditionHandler.java b/api/src/main/java/ru/dragonestia/msb3/api/dialog/condition/AlwaysDialogConditionHandler.java new file mode 100644 index 0000000..daaafaa --- /dev/null +++ b/api/src/main/java/ru/dragonestia/msb3/api/dialog/condition/AlwaysDialogConditionHandler.java @@ -0,0 +1,15 @@ +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.ui.dialogue.DialogueRenderer; + +import java.util.Map; + +public class AlwaysDialogConditionHandler implements DialogConditionHandler { + + @Override + public boolean check(Player player, Dialog dialog, 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..d295a16 --- /dev/null +++ b/api/src/main/java/ru/dragonestia/msb3/api/dialog/condition/DialogConditionHandler.java @@ -0,0 +1,12 @@ +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.ui.dialogue.DialogueRenderer; + +import java.util.Map; + +public interface DialogConditionHandler { + + boolean check(Player player, Dialog dialog, 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..84af135 --- /dev/null +++ b/api/src/main/java/ru/dragonestia/msb3/api/dialog/condition/NeverDialogConditionHandler.java @@ -0,0 +1,15 @@ +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.ui.dialogue.DialogueRenderer; + +import java.util.Map; + +public class NeverDialogConditionHandler implements DialogConditionHandler { + + @Override + public boolean check(Player player, Dialog dialog, 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/player/defaults/TalksContext.java b/api/src/main/java/ru/dragonestia/msb3/api/player/defaults/TalksContext.java new file mode 100644 index 0000000..62e62dd --- /dev/null +++ b/api/src/main/java/ru/dragonestia/msb3/api/player/defaults/TalksContext.java @@ -0,0 +1,38 @@ +package ru.dragonestia.msb3.api.player.defaults; + +import lombok.Getter; +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 { + + 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/ui/TalksThemes.java b/api/src/main/java/ru/dragonestia/msb3/api/ui/TalksThemes.java index 3654381..34a0308 100644 --- a/api/src/main/java/ru/dragonestia/msb3/api/ui/TalksThemes.java +++ b/api/src/main/java/ru/dragonestia/msb3/api/ui/TalksThemes.java @@ -1,21 +1,32 @@ package ru.dragonestia.msb3.api.ui; import lombok.experimental.UtilityClass; +import ru.dragonestia.msb3.api.ui.dialogue.DialogueTheme; import ru.dragonestia.msb3.api.ui.monologue.MonologueTheme; import java.util.HashMap; import java.util.Map; +import java.util.Optional; @UtilityClass public class TalksThemes { private final Map monologueThemes = new HashMap<>(); + private final Map dialogueThemes = new HashMap<>(); public void registerMonologueTheme(String identifier, MonologueTheme theme) { monologueThemes.put(identifier, theme); } - public MonologueTheme getMonologueTheme(String identifier) { - return monologueThemes.get(identifier); + public Optional getMonologueTheme(String identifier) { + return Optional.ofNullable(monologueThemes.get(identifier)); + } + + public void registerDialogueTheme(String identifier, DialogueTheme theme) { + dialogueThemes.put(identifier, theme); + } + + public Optional getDialogueTheme(String identifier) { + return Optional.ofNullable(dialogueThemes.get(identifier)); } } diff --git a/api/src/main/java/ru/dragonestia/msb3/api/ui/dialogue/DialogueRenderer.java b/api/src/main/java/ru/dragonestia/msb3/api/ui/dialogue/DialogueRenderer.java index bc201de..6fb23ba 100644 --- a/api/src/main/java/ru/dragonestia/msb3/api/ui/dialogue/DialogueRenderer.java +++ b/api/src/main/java/ru/dragonestia/msb3/api/ui/dialogue/DialogueRenderer.java @@ -24,6 +24,7 @@ import ru.dragonestia.msb3.api.item.ItemUtil; import ru.dragonestia.msb3.api.resource.dialog.ButtonNumber; import ru.dragonestia.msb3.api.resource.dialog.GlyphPositions; import ru.dragonestia.msb3.api.resource.dialog.TextureProperties; +import ru.dragonestia.msb3.api.util.DebugMessage; import ru.dragonestia.msb3.api.util.StringUtil; import ru.dragonestia.msb3.resource.glyph.GlyphComponent; import ru.dragonestia.msb3.resource.glyph.GlyphComponentBuilder; @@ -32,6 +33,7 @@ import ru.dragonestia.msb3.resource.glyph.MinecraftFont; import java.util.ArrayList; import java.util.EnumMap; import java.util.List; +import java.util.Optional; import java.util.function.Consumer; @Log4j2 @@ -52,6 +54,8 @@ public class DialogueRenderer extends Inventory { private EventListener onCloseListener; private EventListener onClickListener; @Getter @Setter private OnCloseDialog onClose; + private final Object lockButtonExecution = new Object(); + private volatile boolean lockedExecution = false; public DialogueRenderer(Player player, DialogueTheme theme) { super(InventoryType.CHEST_6_ROW, Component.empty()); @@ -221,16 +225,25 @@ public class DialogueRenderer extends Inventory { } case ANSWER_1, ANSWER_2, ANSWER_3, ANSWER_4 -> { - var buttonNumber = type.buttonNumber; - if (onAnswerClick.containsKey(buttonNumber)) { - var ctx = new AnswerClickContext( - player, - this, - buttonNumber, - buttonsText.get(buttonNumber) - ); + if (tryLockButtonActionExecution()) { + try { + var buttonNumber = type.buttonNumber; + if (onAnswerClick.containsKey(buttonNumber)) { + var ctx = new AnswerClickContext( + player, + this, + buttonNumber, + buttonsText.get(buttonNumber) + ); - onAnswerClick.get(buttonNumber).accept(ctx); + onAnswerClick.get(buttonNumber).accept(ctx); + } + } catch (Exception ex) { + DebugMessage.sendError(player, "Во время обработки нажатия кнопки произошла ошибка: " + ex.getMessage()); + log.error(ex.getMessage(), ex); + } finally { + unlockButtonActionExecution(); + } } } } @@ -252,13 +265,17 @@ public class DialogueRenderer extends Inventory { itemBackup = null; } + if (onCloseListener != null) { + player.eventNode().removeListener(onCloseListener); + onCloseListener = null; + } + + if (onClickListener != null) { + player.eventNode().removeListener(onClickListener); + onClickListener = null; + } + if (!closedByPlayer) player.closeInventory(); - - player.eventNode().removeListener(onCloseListener); - onCloseListener = null; - - player.eventNode().removeListener(onClickListener); - onClickListener = null; } if (onClose != null) onClose.onClose(closedByPlayer); @@ -365,7 +382,6 @@ public class DialogueRenderer extends Inventory { return builder.build(); } - private List breakIntoLines(String input, TextureProperties firstLineProperties, int fontHeight, int maxWidth) throws IllegalStateException { var resolvedText = MiniMessage.miniMessage().deserialize(input); var lines = StringUtil.splitIntoParts(resolvedText, maxWidth, line -> { @@ -425,6 +441,25 @@ public class DialogueRenderer extends Inventory { return lines; } + private boolean tryLockButtonActionExecution() { + synchronized (lockButtonExecution) { + if (lockedExecution) return false; // fail + + lockedExecution = true; + return true; // success + } + } + + private void unlockButtonActionExecution() { + synchronized (lockButtonExecution) { + lockedExecution = false; + } + } + + public void removeButton(ButtonNumber buttonNumber) { + setButton(buttonNumber, null, null); + } + public void setButton(ButtonNumber buttonNumber, String text, Consumer onClick) { if (text == null) { buttonsText.remove(buttonNumber); @@ -436,6 +471,15 @@ public class DialogueRenderer extends Inventory { onAnswerClick.put(buttonNumber, onClick); } + public static Optional getRenderer(Player player) { + var inv = player.getOpenInventory(); + if (inv == null) return Optional.empty(); + if (inv instanceof DialogueRenderer renderer) { + return Optional.of(renderer); + } + return Optional.empty(); + } + private record TextLine(DialogueTheme theme, String text, TextureProperties textureProperties) { public GlyphComponent toGlyphList(TextureProperties parentProperties, int lineShift, boolean cutEnding) { diff --git a/api/src/main/java/ru/dragonestia/msb3/api/util/DebugMessage.java b/api/src/main/java/ru/dragonestia/msb3/api/util/DebugMessage.java index 87582af..95afa32 100644 --- a/api/src/main/java/ru/dragonestia/msb3/api/util/DebugMessage.java +++ b/api/src/main/java/ru/dragonestia/msb3/api/util/DebugMessage.java @@ -1,10 +1,12 @@ package ru.dragonestia.msb3.api.util; import lombok.experimental.UtilityClass; +import net.kyori.adventure.sound.Sound; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.TextColor; import net.kyori.adventure.text.format.TextDecoration; import net.minestom.server.entity.Player; +import net.minestom.server.sound.SoundEvent; @UtilityClass public class DebugMessage { @@ -23,6 +25,9 @@ public class DebugMessage { public void sendError(Player player, String message) { if (disabled) return; + var sound = Sound.sound(SoundEvent.BLOCK_NOTE_BLOCK_BIT, Sound.Source.NEUTRAL, 1f, 0f); + player.playSound(sound); + player.sendMessage(Component.text() .append(Component.text("[DEBUG] ", TextColor.color(0xFFC909), TextDecoration.BOLD)) .append(Component.text("Error: ", TextColor.color(0xFF3F3F)))