feat: implemented dialog

This commit is contained in:
Andrey Terentev 2025-03-12 16:13:50 +07:00
parent cbbccbda99
commit 37a0ec22fe
19 changed files with 542 additions and 25 deletions

View File

@ -1,9 +1,14 @@
package ru.dragonestia.msb3.api.boot; package ru.dragonestia.msb3.api.boot;
import lombok.extern.log4j.Log4j2; import lombok.extern.log4j.Log4j2;
import net.kyori.adventure.key.Key;
import net.minestom.server.MinecraftServer; import net.minestom.server.MinecraftServer;
import net.minestom.server.entity.GameMode; import net.minestom.server.entity.GameMode;
import net.minestom.server.event.player.PlayerChatEvent; 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.FlatWorldModule;
import ru.dragonestia.msb3.api.module.MotdModule; import ru.dragonestia.msb3.api.module.MotdModule;
import ru.dragonestia.msb3.api.module.PrometheusMetricsModule; 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 team.unnamed.creative.ResourcePack;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.HashMap;
@Log4j2 @Log4j2
public class DefaultBootstrap extends ServerInitializer { public class DefaultBootstrap extends ServerInitializer {
@ -30,9 +37,10 @@ public class DefaultBootstrap extends ServerInitializer {
MinecraftServer.getGlobalEventHandler().addListener(PlayerChatEvent.class, event -> { MinecraftServer.getGlobalEventHandler().addListener(PlayerChatEvent.class, event -> {
var player = event.getPlayer(); var player = event.getPlayer();
var dialog = new Dialog();
var render = new DialogueRenderer(player, DialogueTheme.builder().build()); dialog.setId(Key.key("msb3", "test_dialog"));
render.setText(""" dialog.setText("""
Абсолютно точно. Абсолютно точно.
Я знаю точнo - невозможное возможно Я знаю точнo - невозможное возможно
Сойти с ума, влюбиться так неосторoжно Сойти с ума, влюбиться так неосторoжно
@ -64,13 +72,15 @@ public class DefaultBootstrap extends ServerInitializer {
Всё невозможное - возможно, знаю точно! Всё невозможное - возможно, знаю точно!
На-на-на-на (на-на-на-на), а-а, а-а На-на-на-на (на-на-на-на), а-а, а-а
На-на-на-на (на-на-на-на), а-а, а-а"""); На-на-на-на (на-на-на-на), а-а, а-а""");
dialog.setRememberId(true);
render.setButton(ButtonNumber.BUTTON_1, "Всем привет!", ctx -> {}); var buttons = new ArrayList<DialogButton>();
render.setButton(ButtonNumber.BUTTON_2, "I am a teapot", ctx -> {}); buttons.add(new DialogButton(null, "Какая-то кнопочка", null, new HashMap<>(), new ArrayList<>()));
render.setButton(ButtonNumber.BUTTON_3, "I love pizza\nMamma mia\nPeperoni\nPapa carlo\nZaebumba\nZaebumba", ctx -> {}); buttons.add(new DialogButton(Key.key("msb3", "test_button"), "2 Какая-то кнопочка", "aboba", new HashMap<>(), new ArrayList<>()));
render.setButton(ButtonNumber.BUTTON_4, "msb3 is top!", ctx -> {}); buttons.add(new DialogButton(null, "Третья", "close", new HashMap<>(), new ArrayList<>()));
dialog.setButtons(buttons);
render.show(); dialog.open(player);
}); });
} }

View File

@ -8,6 +8,10 @@ import net.minestom.server.MinecraftServer;
import net.minestom.server.event.player.AsyncPlayerConfigurationEvent; import net.minestom.server.event.player.AsyncPlayerConfigurationEvent;
import net.minestom.server.event.player.PlayerDisconnectEvent; import net.minestom.server.event.player.PlayerDisconnectEvent;
import net.minestom.server.event.player.PlayerSpawnEvent; 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.entity.PickableItem;
import ru.dragonestia.msb3.api.item.ItemUtil; import ru.dragonestia.msb3.api.item.ItemUtil;
import ru.dragonestia.msb3.api.player.MsbPlayer; import ru.dragonestia.msb3.api.player.MsbPlayer;
@ -89,6 +93,8 @@ public final class ServerBootstrap {
initPlayerContextManager(); initPlayerContextManager();
initDefaultSkins(); initDefaultSkins();
initDefaultDialogActionsAndConditions();
} }
private void initDefaultModules() { private void initDefaultModules() {
@ -188,4 +194,11 @@ public final class ServerBootstrap {
private void initDefaultSkins() { private void initDefaultSkins() {
SkinStorage.loadSkin(SkinStorage.DEFAULT, ResourceFromJar.of("skins/default.msb3skin")); 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());
}
} }

View File

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

View File

@ -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<String, String> params;
private List<DialogCondition> 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();
}
}

View File

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

View File

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

View File

@ -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<Key, Dialog> dialogs = new ConcurrentHashMap<>();
private final Map<String, DialogActionHandler> actionHandlers = new HashMap<>();
private final Map<String, DialogConditionHandler> 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<Dialog> 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<DialogActionHandler> 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<DialogConditionHandler> findConditionHandler(String id) {
return Optional.ofNullable(conditionHandlers.get(id));
}
}

View File

@ -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<String, String> params) {
click.renderer().close(false);
}
}

View File

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

View File

@ -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<String, String> params) {
return true;
}
}

View File

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

View File

@ -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<String, String> params) {
return false;
}
}

View File

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

View File

@ -0,0 +1,8 @@
package ru.dragonestia.msb3.api.dialog.data;
import java.util.Set;
public record TalksPlayerData(
Set<String> openedDialogs,
Set<String> clickedButtons
) {}

View File

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

View File

@ -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<String> openedDialogues = Collections.synchronizedSet(new HashSet<>());
private final Set<String> 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
));
}
}

View File

@ -1,21 +1,32 @@
package ru.dragonestia.msb3.api.ui; package ru.dragonestia.msb3.api.ui;
import lombok.experimental.UtilityClass; import lombok.experimental.UtilityClass;
import ru.dragonestia.msb3.api.ui.dialogue.DialogueTheme;
import ru.dragonestia.msb3.api.ui.monologue.MonologueTheme; import ru.dragonestia.msb3.api.ui.monologue.MonologueTheme;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Optional;
@UtilityClass @UtilityClass
public class TalksThemes { public class TalksThemes {
private final Map<String, MonologueTheme> monologueThemes = new HashMap<>(); private final Map<String, MonologueTheme> monologueThemes = new HashMap<>();
private final Map<String, DialogueTheme> dialogueThemes = new HashMap<>();
public void registerMonologueTheme(String identifier, MonologueTheme theme) { public void registerMonologueTheme(String identifier, MonologueTheme theme) {
monologueThemes.put(identifier, theme); monologueThemes.put(identifier, theme);
} }
public MonologueTheme getMonologueTheme(String identifier) { public Optional<MonologueTheme> getMonologueTheme(String identifier) {
return monologueThemes.get(identifier); return Optional.ofNullable(monologueThemes.get(identifier));
}
public void registerDialogueTheme(String identifier, DialogueTheme theme) {
dialogueThemes.put(identifier, theme);
}
public Optional<DialogueTheme> getDialogueTheme(String identifier) {
return Optional.ofNullable(dialogueThemes.get(identifier));
} }
} }

View File

@ -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.ButtonNumber;
import ru.dragonestia.msb3.api.resource.dialog.GlyphPositions; import ru.dragonestia.msb3.api.resource.dialog.GlyphPositions;
import ru.dragonestia.msb3.api.resource.dialog.TextureProperties; 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.api.util.StringUtil;
import ru.dragonestia.msb3.resource.glyph.GlyphComponent; import ru.dragonestia.msb3.resource.glyph.GlyphComponent;
import ru.dragonestia.msb3.resource.glyph.GlyphComponentBuilder; 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.ArrayList;
import java.util.EnumMap; import java.util.EnumMap;
import java.util.List; import java.util.List;
import java.util.Optional;
import java.util.function.Consumer; import java.util.function.Consumer;
@Log4j2 @Log4j2
@ -52,6 +54,8 @@ public class DialogueRenderer extends Inventory {
private EventListener<InventoryCloseEvent> onCloseListener; private EventListener<InventoryCloseEvent> onCloseListener;
private EventListener<InventoryPreClickEvent> onClickListener; private EventListener<InventoryPreClickEvent> onClickListener;
@Getter @Setter private OnCloseDialog onClose; @Getter @Setter private OnCloseDialog onClose;
private final Object lockButtonExecution = new Object();
private volatile boolean lockedExecution = false;
public DialogueRenderer(Player player, DialogueTheme theme) { public DialogueRenderer(Player player, DialogueTheme theme) {
super(InventoryType.CHEST_6_ROW, Component.empty()); 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 -> { case ANSWER_1, ANSWER_2, ANSWER_3, ANSWER_4 -> {
var buttonNumber = type.buttonNumber; if (tryLockButtonActionExecution()) {
if (onAnswerClick.containsKey(buttonNumber)) { try {
var ctx = new AnswerClickContext( var buttonNumber = type.buttonNumber;
player, if (onAnswerClick.containsKey(buttonNumber)) {
this, var ctx = new AnswerClickContext(
buttonNumber, player,
buttonsText.get(buttonNumber) 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; itemBackup = null;
} }
if (onCloseListener != null) {
player.eventNode().removeListener(onCloseListener);
onCloseListener = null;
}
if (onClickListener != null) {
player.eventNode().removeListener(onClickListener);
onClickListener = null;
}
if (!closedByPlayer) player.closeInventory(); if (!closedByPlayer) player.closeInventory();
player.eventNode().removeListener(onCloseListener);
onCloseListener = null;
player.eventNode().removeListener(onClickListener);
onClickListener = null;
} }
if (onClose != null) onClose.onClose(closedByPlayer); if (onClose != null) onClose.onClose(closedByPlayer);
@ -365,7 +382,6 @@ public class DialogueRenderer extends Inventory {
return builder.build(); return builder.build();
} }
private List<TextLine> breakIntoLines(String input, TextureProperties firstLineProperties, int fontHeight, int maxWidth) throws IllegalStateException { private List<TextLine> breakIntoLines(String input, TextureProperties firstLineProperties, int fontHeight, int maxWidth) throws IllegalStateException {
var resolvedText = MiniMessage.miniMessage().deserialize(input); var resolvedText = MiniMessage.miniMessage().deserialize(input);
var lines = StringUtil.splitIntoParts(resolvedText, maxWidth, line -> { var lines = StringUtil.splitIntoParts(resolvedText, maxWidth, line -> {
@ -425,6 +441,25 @@ public class DialogueRenderer extends Inventory {
return lines; 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<AnswerClickContext> onClick) { public void setButton(ButtonNumber buttonNumber, String text, Consumer<AnswerClickContext> onClick) {
if (text == null) { if (text == null) {
buttonsText.remove(buttonNumber); buttonsText.remove(buttonNumber);
@ -436,6 +471,15 @@ public class DialogueRenderer extends Inventory {
onAnswerClick.put(buttonNumber, onClick); onAnswerClick.put(buttonNumber, onClick);
} }
public static Optional<DialogueRenderer> 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) { private record TextLine(DialogueTheme theme, String text, TextureProperties textureProperties) {
public GlyphComponent toGlyphList(TextureProperties parentProperties, int lineShift, boolean cutEnding) { public GlyphComponent toGlyphList(TextureProperties parentProperties, int lineShift, boolean cutEnding) {

View File

@ -1,10 +1,12 @@
package ru.dragonestia.msb3.api.util; package ru.dragonestia.msb3.api.util;
import lombok.experimental.UtilityClass; import lombok.experimental.UtilityClass;
import net.kyori.adventure.sound.Sound;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.TextColor; import net.kyori.adventure.text.format.TextColor;
import net.kyori.adventure.text.format.TextDecoration; import net.kyori.adventure.text.format.TextDecoration;
import net.minestom.server.entity.Player; import net.minestom.server.entity.Player;
import net.minestom.server.sound.SoundEvent;
@UtilityClass @UtilityClass
public class DebugMessage { public class DebugMessage {
@ -23,6 +25,9 @@ public class DebugMessage {
public void sendError(Player player, String message) { public void sendError(Player player, String message) {
if (disabled) return; if (disabled) return;
var sound = Sound.sound(SoundEvent.BLOCK_NOTE_BLOCK_BIT, Sound.Source.NEUTRAL, 1f, 0f);
player.playSound(sound);
player.sendMessage(Component.text() player.sendMessage(Component.text()
.append(Component.text("[DEBUG] ", TextColor.color(0xFFC909), TextDecoration.BOLD)) .append(Component.text("[DEBUG] ", TextColor.color(0xFFC909), TextDecoration.BOLD))
.append(Component.text("Error: ", TextColor.color(0xFF3F3F))) .append(Component.text("Error: ", TextColor.color(0xFF3F3F)))