feat: Dialog answering

This commit is contained in:
2026-01-19 20:17:13 +01:00
parent eef269c853
commit da45765413
3 changed files with 144 additions and 23 deletions

View File

@@ -7,7 +7,6 @@ import cz.jzitnik.annotations.EventHandler;
import cz.jzitnik.annotations.injectors.InjectDependency; import cz.jzitnik.annotations.injectors.InjectDependency;
import cz.jzitnik.annotations.injectors.InjectState; import cz.jzitnik.annotations.injectors.InjectState;
import cz.jzitnik.events.RerenderScreen; import cz.jzitnik.events.RerenderScreen;
import cz.jzitnik.game.GameState;
import cz.jzitnik.game.dialog.Dialog; import cz.jzitnik.game.dialog.Dialog;
import cz.jzitnik.game.dialog.OnEnd; import cz.jzitnik.game.dialog.OnEnd;
import cz.jzitnik.states.DialogState; import cz.jzitnik.states.DialogState;
@@ -16,7 +15,6 @@ import cz.jzitnik.states.TerminalState;
import cz.jzitnik.ui.pixels.AlphaPixel; import cz.jzitnik.ui.pixels.AlphaPixel;
import cz.jzitnik.ui.pixels.ColoredPixel; import cz.jzitnik.ui.pixels.ColoredPixel;
import cz.jzitnik.ui.pixels.Empty; import cz.jzitnik.ui.pixels.Empty;
import cz.jzitnik.ui.pixels.Pixel;
import cz.jzitnik.utils.DependencyManager; import cz.jzitnik.utils.DependencyManager;
import cz.jzitnik.utils.TextRenderer; import cz.jzitnik.utils.TextRenderer;
import cz.jzitnik.utils.events.AbstractEventHandler; import cz.jzitnik.utils.events.AbstractEventHandler;
@@ -49,22 +47,34 @@ public class DialogEventHandler extends AbstractEventHandler<Dialog> {
@InjectDependency @InjectDependency
private TextRenderer textRenderer; private TextRenderer textRenderer;
private static final int WIDTH = 350;
private static final int MARGIN_BOTTOM = 15; private static final int MARGIN_BOTTOM = 15;
private static final int PADDING = 7; public static final int PADDING = 7;
private static final int BUTTON_TEXT_PADDING = 4; private static final int BUTTON_TEXT_PADDING = 4;
private static final int QUESTION_ACTIONS_GAP = 10; private static final int QUESTION_ACTIONS_GAP = 10;
private static final int BUTTON_HEIGHT = 15; public static final int BUTTON_HEIGHT = 15;
private static final int BUTTON_PADDING = 5; public static final int BUTTON_PADDING = 5;
private static final float FONT_SIZE = 15f; private static final float FONT_SIZE = 15f;
public static TerminalSize getSize(TextRenderer textRenderer, Dialog dialog) { public static int calculateButtonHeight(Dialog dialog) {
int WIDTH = 355; if (dialog.getOnEnd() instanceof OnEnd.AskQuestion(OnEnd.AskQuestion.Answer[] answers)) {
return answers.length * BUTTON_HEIGHT + (answers.length - 1) * BUTTON_PADDING;
} else {
return 0;
}
}
public static int getYStartButtons(TextRenderer textRenderer, Dialog dialog) {
var textSize = textRenderer.measureText(dialog.getText(), WIDTH - PADDING * 2, FONT_SIZE); var textSize = textRenderer.measureText(dialog.getText(), WIDTH - PADDING * 2, FONT_SIZE);
return new TerminalSize(355, PADDING + textSize.height + ( return PADDING + textSize.height + BUTTON_PADDING;
dialog.getOnEnd() instanceof OnEnd.AskQuestion( }
OnEnd.AskQuestion.Answer[] answers
) ? answers.length * BUTTON_HEIGHT + (answers.length - 1) * BUTTON_PADDING : 0 public static TerminalSize getSize(TextRenderer textRenderer, Dialog dialog) {
var textSize = textRenderer.measureText(dialog.getText(), WIDTH - PADDING * 2, FONT_SIZE);
return new TerminalSize(300, PADDING + textSize.height + (
dialog.getOnEnd() instanceof OnEnd.AskQuestion ? BUTTON_PADDING + calculateButtonHeight(dialog) : 0
) + PADDING); ) + PADDING);
} }
@@ -77,6 +87,7 @@ public class DialogEventHandler extends AbstractEventHandler<Dialog> {
@Override @Override
public void handle(Dialog event) { public void handle(Dialog event) {
boolean onlyLast = dialogState.getCurrentDialog() == event;
dialogState.setCurrentDialog(event); dialogState.setCurrentDialog(event);
TerminalSize terminalSize = terminalState.getTerminalScreen().getTerminalSize(); TerminalSize terminalSize = terminalState.getTerminalScreen().getTerminalSize();
var overrideBuffer = screenBuffer.getGlobalOverrideBuffer(); var overrideBuffer = screenBuffer.getGlobalOverrideBuffer();
@@ -98,13 +109,15 @@ public class DialogEventHandler extends AbstractEventHandler<Dialog> {
} }
} }
dialogState.setRenderInProgress(true);
try { try {
for (AlphaPixel[][] buf : animation) { for (int i = onlyLast ? animation.length : 0; i <= animation.length; i++) {
var buf = animation[Math.min(i, animation.length - 1)];
for (int y = 0; y < size.getRows(); y++) { for (int y = 0; y < size.getRows(); y++) {
for (int x = 0; x < size.getColumns(); x++) { for (int x = 0; x < size.getColumns(); x++) {
var textPixel = buf[Math.min(Math.max(0, y - PADDING), buf.length - 1)][Math.min(Math.max(0, x - PADDING), buf[0].length - 1)]; var textPixel = buf[Math.min(Math.max(0, y - PADDING), buf.length - 1)][Math.min(Math.max(0, x - PADDING), buf[0].length - 1)];
if (textPixel instanceof Empty || y < PADDING || x < PADDING || x >= size.getColumns() - PADDING || y >= size.getRows() - PADDING) { if (textPixel instanceof Empty || y < PADDING || x < PADDING || x >= size.getColumns() - PADDING || y >= size.getRows() - PADDING) {
if (y - 2 > textSize.height + QUESTION_ACTIONS_GAP && onEnd instanceof OnEnd.AskQuestion( if (i == animation.length && y - 2 > textSize.height + QUESTION_ACTIONS_GAP && onEnd instanceof OnEnd.AskQuestion(
OnEnd.AskQuestion.Answer[] answers OnEnd.AskQuestion.Answer[] answers
)) { )) {
int buttonsY = y - textSize.height - QUESTION_ACTIONS_GAP - 2; int buttonsY = y - textSize.height - QUESTION_ACTIONS_GAP - 2;
@@ -118,7 +131,7 @@ public class DialogEventHandler extends AbstractEventHandler<Dialog> {
var buttonTextPixel = buttonBuf[Math.min(Math.max(0, localY), buttonBuf.length - 1)][Math.min(Math.max(0, localX), buttonBuf[0].length - 1)]; var buttonTextPixel = buttonBuf[Math.min(Math.max(0, localY), buttonBuf.length - 1)][Math.min(Math.max(0, localX), buttonBuf[0].length - 1)];
if (buttonTextPixel instanceof Empty || localY < 0 || localX < 0 || localY >= buttonBuf.length || localX >= buttonBuf[0].length) { if (buttonTextPixel instanceof Empty || localY < 0 || localX < 0 || localY >= buttonBuf.length || localX >= buttonBuf[0].length) {
overrideBuffer[start.getRow() + y][start.getColumn() + x] = new ColoredPixel(new TextColor.RGB(255, 255, 255), 1f); overrideBuffer[start.getRow() + y][start.getColumn() + x] = new ColoredPixel(new TextColor.RGB(255, 255, 255), dialogState.getHoveredButtonIndex() == buttonIndex ? 0.8f : 0.6f);
} else { } else {
overrideBuffer[start.getRow() + y][start.getColumn() + x] = buttonTextPixel; overrideBuffer[start.getRow() + y][start.getColumn() + x] = buttonTextPixel;
} }
@@ -147,11 +160,29 @@ public class DialogEventHandler extends AbstractEventHandler<Dialog> {
Thread.sleep(1000 / event.getTypingSpeed()); Thread.sleep(1000 / event.getTypingSpeed());
} }
dialogState.setRenderInProgress(false);
if (onEnd instanceof OnEnd.Continue(Dialog nextDialog)) { if (onEnd instanceof OnEnd.Continue(Dialog nextDialog)) {
Thread.sleep(1000); Thread.sleep(1000);
eventManager.emitEvent(nextDialog); for (int y = start.getRow(); y < start.getRow() + size.getRows(); y++) {
} else if (onEnd instanceof OnEnd.AskQuestion(OnEnd.AskQuestion.Answer[] answers)) { for (int x = start.getColumn(); x < start.getColumn() + size.getColumns(); x++) {
screenBuffer.getGlobalOverrideBuffer()[y][x] = new Empty();
}
}
if (nextDialog == null) {
dialogState.setCurrentDialog(null);
eventManager.emitEvent(
new RerenderScreen(
new RerenderScreen.ScreenPart(
start,
new TerminalPosition(start.getColumn() + size.getColumns(), start.getRow() + size.getRows())
)
)
);
} else {
eventManager.emitEvent(nextDialog);
}
} }
} catch (InterruptedException e) { } catch (InterruptedException e) {
throw new RuntimeException(e); throw new RuntimeException(e);

View File

@@ -8,4 +8,6 @@ import lombok.Data;
@Data @Data
public class DialogState { public class DialogState {
private Dialog currentDialog; private Dialog currentDialog;
private boolean renderInProgress = false;
private int hoveredButtonIndex = -1;
} }

View File

@@ -11,11 +11,18 @@ import cz.jzitnik.annotations.ui.UI;
import cz.jzitnik.events.MouseAction; import cz.jzitnik.events.MouseAction;
import cz.jzitnik.events.RerenderScreen; import cz.jzitnik.events.RerenderScreen;
import cz.jzitnik.events.handlers.DialogEventHandler; import cz.jzitnik.events.handlers.DialogEventHandler;
import cz.jzitnik.game.dialog.OnEnd;
import cz.jzitnik.states.DialogState; import cz.jzitnik.states.DialogState;
import cz.jzitnik.states.ScreenBuffer;
import cz.jzitnik.states.TerminalState; import cz.jzitnik.states.TerminalState;
import cz.jzitnik.ui.pixels.Empty;
import cz.jzitnik.utils.TextRenderer; import cz.jzitnik.utils.TextRenderer;
import cz.jzitnik.utils.events.Event;
import cz.jzitnik.utils.events.EventManager;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import static cz.jzitnik.events.handlers.DialogEventHandler.*;
@Slf4j @Slf4j
@UI @UI
@Dependency @Dependency
@@ -29,48 +36,129 @@ public class DialogUI {
@InjectDependency @InjectDependency
private TextRenderer textRenderer; private TextRenderer textRenderer;
@InjectDependency
private EventManager eventManager;
@InjectState
private ScreenBuffer screenBuffer;
@MouseHandler(MouseHandlerType.CLICK) @MouseHandler(MouseHandlerType.CLICK)
public boolean handleClick(MouseAction mouseAction) { public boolean handleClick(MouseAction mouseAction) {
if (dialogState.getCurrentDialog() == null) { if (dialogState.getCurrentDialog() == null || dialogState.isRenderInProgress()) {
return false; return false;
} }
TerminalSize size = DialogEventHandler.getSize(textRenderer, dialogState.getCurrentDialog()); TerminalSize size = DialogEventHandler.getSize(textRenderer, dialogState.getCurrentDialog());
TerminalPosition start = DialogEventHandler.getStart(terminalState.getTerminalScreen().getTerminalSize(), size); TerminalPosition start = DialogEventHandler.getStart(terminalState.getTerminalScreen().getTerminalSize(), size);
if (!(dialogState.getCurrentDialog().getOnEnd() instanceof OnEnd.AskQuestion(
OnEnd.AskQuestion.Answer[] answers
))) {
setHoveredButtonIndex(-1);
return false;
}
TerminalPosition mouse = mouseAction.getPosition(); TerminalPosition mouse = mouseAction.getPosition();
TerminalPosition mouseNormalized = new TerminalPosition(mouse.getColumn(), mouse.getRow() * 2); TerminalPosition mouseNormalized = new TerminalPosition(mouse.getColumn(), mouse.getRow() * 2);
;
RerenderScreen.ScreenPart part = new RerenderScreen.ScreenPart( RerenderScreen.ScreenPart part = new RerenderScreen.ScreenPart(
start, start,
new TerminalPosition(start.getColumn() + size.getColumns(), start.getRow() + size.getRows()) new TerminalPosition(start.getColumn() + size.getColumns(), start.getRow() + size.getRows())
); );
if (!part.isWithin(mouseNormalized)) { if (!part.isWithin(mouseNormalized)) {
setHoveredButtonIndex(-1);
return false; return false;
} }
TerminalPosition localPosition = new TerminalPosition(mouseNormalized.getColumn() - start.getColumn(), mouseNormalized.getRow() - start.getRow());
return false; int buttonsStartY = DialogEventHandler.getYStartButtons(textRenderer, dialogState.getCurrentDialog());
TerminalPosition localPosition = new TerminalPosition(mouseNormalized.getColumn() - start.getColumn(), mouseNormalized.getRow() - start.getRow() - buttonsStartY);
int buttonsHeight = DialogEventHandler.calculateButtonHeight(dialogState.getCurrentDialog());
if (localPosition.getRow() < 0 || localPosition.getRow() >= buttonsHeight) {
setHoveredButtonIndex(-1);
return true;
}
int buttonIndex = localPosition.getRow() / (BUTTON_HEIGHT + BUTTON_PADDING);
int rest = localPosition.getRow() % (BUTTON_HEIGHT + BUTTON_PADDING);
if (buttonIndex < answers.length && rest < BUTTON_HEIGHT && localPosition.getColumn() >= PADDING && localPosition.getColumn() < size.getColumns() - PADDING) {
for (int y = start.getRow(); y < start.getRow() + size.getRows(); y++) {
for (int x = start.getColumn(); x < start.getColumn() + size.getColumns(); x++) {
screenBuffer.getGlobalOverrideBuffer()[y][x] = new Empty();
}
}
eventManager.emitEvent(
new Event[]{
new RerenderScreen(
new RerenderScreen.ScreenPart(
start,
new TerminalPosition(start.getColumn() + size.getColumns(), start.getRow() + size.getRows())
)
),
answers[buttonIndex].dialog(),
});
return true;
}
return true;
} }
@MouseHandler(MouseHandlerType.MOVE) @MouseHandler(MouseHandlerType.MOVE)
public boolean handleMove(MouseAction mouseAction) { public boolean handleMove(MouseAction mouseAction) {
if (dialogState.getCurrentDialog() == null) { if (dialogState.getCurrentDialog() == null || dialogState.isRenderInProgress()) {
return false; return false;
} }
TerminalSize size = DialogEventHandler.getSize(textRenderer, dialogState.getCurrentDialog()); TerminalSize size = DialogEventHandler.getSize(textRenderer, dialogState.getCurrentDialog());
TerminalPosition start = DialogEventHandler.getStart(terminalState.getTerminalScreen().getTerminalSize(), size); TerminalPosition start = DialogEventHandler.getStart(terminalState.getTerminalScreen().getTerminalSize(), size);
if (!(dialogState.getCurrentDialog().getOnEnd() instanceof OnEnd.AskQuestion(
OnEnd.AskQuestion.Answer[] answers
))) {
setHoveredButtonIndex(-1);
return false;
}
TerminalPosition mouse = mouseAction.getPosition(); TerminalPosition mouse = mouseAction.getPosition();
TerminalPosition mouseNormalized = new TerminalPosition(mouse.getColumn(), mouse.getRow() * 2); TerminalPosition mouseNormalized = new TerminalPosition(mouse.getColumn(), mouse.getRow() * 2);
RerenderScreen.ScreenPart part = new RerenderScreen.ScreenPart( RerenderScreen.ScreenPart part = new RerenderScreen.ScreenPart(
start, start,
new TerminalPosition(start.getColumn() + size.getColumns(), start.getRow() + size.getRows()) new TerminalPosition(start.getColumn() + size.getColumns(), start.getRow() + size.getRows())
); );
if (!part.isWithin(mouseNormalized)) { if (!part.isWithin(mouseNormalized)) {
setHoveredButtonIndex(-1);
return false; return false;
} }
TerminalPosition localPosition = new TerminalPosition(mouseNormalized.getColumn() - start.getColumn(), mouseNormalized.getRow() - start.getRow());
log.debug("Position: {}", localPosition); int buttonsStartY = DialogEventHandler.getYStartButtons(textRenderer, dialogState.getCurrentDialog());
TerminalPosition localPosition = new TerminalPosition(mouseNormalized.getColumn() - start.getColumn(), mouseNormalized.getRow() - start.getRow() - buttonsStartY);
int buttonsHeight = DialogEventHandler.calculateButtonHeight(dialogState.getCurrentDialog());
if (localPosition.getRow() < 0 || localPosition.getRow() >= buttonsHeight) {
setHoveredButtonIndex(-1);
return true; return true;
} }
int buttonIndex = localPosition.getRow() / (BUTTON_HEIGHT + BUTTON_PADDING);
int rest = localPosition.getRow() % (BUTTON_HEIGHT + BUTTON_PADDING);
if (buttonIndex < answers.length && rest < BUTTON_HEIGHT && localPosition.getColumn() >= PADDING && localPosition.getColumn() < size.getColumns() - PADDING) {
setHoveredButtonIndex(buttonIndex);
return true;
}
setHoveredButtonIndex(-1);
return true;
}
private void setHoveredButtonIndex(int index) {
if (dialogState.getHoveredButtonIndex() != index) {
dialogState.setHoveredButtonIndex(index);
eventManager.emitEvent(dialogState.getCurrentDialog());
}
}
} }