diff --git a/.idea/compiler.xml b/.idea/compiler.xml index 9abc593..fd0efb3 100644 --- a/.idea/compiler.xml +++ b/.idea/compiler.xml @@ -34,6 +34,7 @@ + diff --git a/game/src/main/java/cz/jzitnik/client/events/handlers/DialogEventHandler.java b/game/src/main/java/cz/jzitnik/client/events/handlers/DialogEventHandler.java index 2a66b69..cfd7deb 100644 --- a/game/src/main/java/cz/jzitnik/client/events/handlers/DialogEventHandler.java +++ b/game/src/main/java/cz/jzitnik/client/events/handlers/DialogEventHandler.java @@ -7,6 +7,7 @@ import cz.jzitnik.client.annotations.EventHandler; import cz.jzitnik.client.annotations.injectors.InjectDependency; import cz.jzitnik.client.annotations.injectors.InjectState; import cz.jzitnik.client.events.RerenderScreen; +import cz.jzitnik.client.game.GameState; import cz.jzitnik.client.game.dialog.Dialog; import cz.jzitnik.client.game.dialog.OnEnd; import cz.jzitnik.client.states.DialogState; @@ -22,8 +23,8 @@ import cz.jzitnik.client.utils.events.EventManager; import lombok.extern.slf4j.Slf4j; import java.awt.*; -import java.util.List; import java.util.ArrayList; +import java.util.List; @Slf4j @EventHandler(Dialog.class) @@ -38,6 +39,9 @@ public class DialogEventHandler extends AbstractEventHandler { @InjectState private ScreenBuffer screenBuffer; + @InjectState + private GameState gameState; + @InjectDependency private EventManager eventManager; @@ -56,32 +60,45 @@ public class DialogEventHandler extends AbstractEventHandler { public static final int BUTTON_PADDING = 5; private static final float FONT_SIZE = 15f; - public static int calculateButtonHeight(Dialog dialog) { - 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 calculateButtonHeight(Dialog dialog, GameState gameState) { + if (dialog.getOnEnd() instanceof OnEnd.AskQuestion askQuestion) { + int count = askQuestion.answers(gameState).length; + return count * BUTTON_HEIGHT + Math.max(0, count - 1) * BUTTON_PADDING; } + 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 PADDING + textSize.height + BUTTON_PADDING; } - public static TerminalSize getSize(TextRenderer textRenderer, Dialog dialog) { - var textSize = textRenderer.measureText(dialog.getText(), WIDTH - PADDING * 2, FONT_SIZE); + public static TerminalSize getSize(TextRenderer textRenderer, Dialog dialog, GameState gameState) { + 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); + int buttonsHeight = 0; + if (dialog.getOnEnd() instanceof OnEnd.AskQuestion) { + buttonsHeight = BUTTON_PADDING + calculateButtonHeight(dialog, gameState); + } + + return new TerminalSize( + 300, + PADDING + textSize.height + buttonsHeight + PADDING + ); } public static TerminalPosition getStart(TerminalSize terminalSize, TerminalSize size) { int startY = terminalSize.getRows() * 2 - MARGIN_BOTTOM - size.getRows(); int startX = (terminalSize.getColumns() / 2) - (size.getColumns() / 2); - return new TerminalPosition(startX, startY); } @@ -89,58 +106,114 @@ public class DialogEventHandler extends AbstractEventHandler { public void handle(Dialog event) { boolean onlyLast = dialogState.getCurrentDialog() == event; dialogState.setCurrentDialog(event); + TerminalSize terminalSize = terminalState.getTerminalScreen().getTerminalSize(); var overrideBuffer = screenBuffer.getGlobalOverrideBuffer(); - var size = getSize(textRenderer, event); - + var size = getSize(textRenderer, event, gameState); var start = getStart(terminalSize, size); - var animation = textRenderer.renderTypingAnimation(event.getText(), size.getColumns() - PADDING * 2, size.getRows() - PADDING * 2, Color.WHITE, FONT_SIZE); - var textSize = textRenderer.measureText(event.getText(), size.getColumns() - PADDING * 2, FONT_SIZE); + var animation = textRenderer.renderTypingAnimation( + event.getText(), + size.getColumns() - PADDING * 2, + size.getRows() - PADDING * 2, + Color.WHITE, + FONT_SIZE + ); + + var textSize = textRenderer.measureText( + event.getText(), + size.getColumns() - PADDING * 2, + FONT_SIZE + ); + OnEnd onEnd = event.getOnEnd(); List answersBuf = new ArrayList<>(); + OnEnd.AskQuestion askQuestion = null; - if (onEnd instanceof OnEnd.AskQuestion( - OnEnd.AskQuestion.Answer[] answers - )) { - for (OnEnd.AskQuestion.Answer answer : answers) { - answersBuf.add(textRenderer.renderText(answer.answer(), size.getColumns() - PADDING * 2, BUTTON_HEIGHT, Color.BLACK, FONT_SIZE, false)); + if (onEnd instanceof OnEnd.AskQuestion aq) { + askQuestion = aq; + for (OnEnd.AskQuestion.Answer answer : aq.answers(gameState)) { + answersBuf.add( + textRenderer.renderText( + answer.answer(), + size.getColumns() - PADDING * 2, + BUTTON_HEIGHT, + Color.BLACK, + FONT_SIZE, + false + ) + ); } } dialogState.setRenderInProgress(true); + try { 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 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)]; - if (textPixel instanceof Empty || y < PADDING || x < PADDING || x >= size.getColumns() - PADDING || y >= size.getRows() - PADDING) { - if (i == animation.length && y - 2 > textSize.height + QUESTION_ACTIONS_GAP && onEnd instanceof OnEnd.AskQuestion( - OnEnd.AskQuestion.Answer[] answers - )) { + + 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 (i == animation.length + && askQuestion != null + && y - 2 > textSize.height + QUESTION_ACTIONS_GAP) { + + var answers = askQuestion.answers(gameState); + int buttonsY = y - textSize.height - QUESTION_ACTIONS_GAP - 2; int buttonIndex = buttonsY / (BUTTON_HEIGHT + BUTTON_PADDING); int rest = buttonsY % (BUTTON_HEIGHT + BUTTON_PADDING); - if (buttonIndex < answers.length && rest < BUTTON_HEIGHT && x >= PADDING && x < size.getColumns() - PADDING) { + if (buttonIndex < answers.length + && rest < BUTTON_HEIGHT + && x >= PADDING + && x < size.getColumns() - PADDING) { + int localY = rest - BUTTON_TEXT_PADDING; int localX = x - PADDING - BUTTON_TEXT_PADDING; + var buttonBuf = answersBuf.get(buttonIndex); - 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) { - overrideBuffer[start.getRow() + y][start.getColumn() + x] = new ColoredPixel(new TextColor.RGB(255, 255, 255), dialogState.getHoveredButtonIndex() == buttonIndex ? 0.8f : 0.6f); + 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), + dialogState.getHoveredButtonIndex() == buttonIndex ? 0.8f : 0.6f + ); } else { - overrideBuffer[start.getRow() + y][start.getColumn() + x] = buttonTextPixel; + overrideBuffer[start.getRow() + y][start.getColumn() + x] = + buttonTextPixel; } - continue; } } - overrideBuffer[start.getRow() + y][start.getColumn() + x] = new ColoredPixel(new TextColor.RGB(0, 0, 0), 0.6f); + overrideBuffer[start.getRow() + y][start.getColumn() + x] = + new ColoredPixel(new TextColor.RGB(0, 0, 0), 0.6f); continue; } @@ -152,7 +225,10 @@ public class DialogEventHandler extends AbstractEventHandler { new RerenderScreen( new RerenderScreen.ScreenPart( start, - new TerminalPosition(start.getColumn() + size.getColumns(), start.getRow() + size.getRows()) + new TerminalPosition( + start.getColumn() + size.getColumns(), + start.getRow() + size.getRows() + ) ) ) ); @@ -161,7 +237,6 @@ public class DialogEventHandler extends AbstractEventHandler { } dialogState.setRenderInProgress(false); - next(onEnd, start, size); } catch (InterruptedException e) { @@ -171,34 +246,38 @@ public class DialogEventHandler extends AbstractEventHandler { private void next(OnEnd onEnd, TerminalPosition start, TerminalSize size) throws InterruptedException { Thread.sleep(1000); - if (onEnd instanceof OnEnd.Continue(Dialog nextDialog)) { - 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(); - } - } + if (onEnd instanceof OnEnd.Continue(Dialog nextDialog)) { + clear(start, size); eventManager.emitEvent(nextDialog); + } else if (onEnd instanceof OnEnd.RunCode(Runnable runnable, OnEnd end)) { dependencyManager.inject(runnable); runnable.run(); next(end, start, size); - } else if (onEnd instanceof OnEnd.End) { - 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(); - } - } + } else if (onEnd instanceof OnEnd.End) { + clear(start, size); dialogState.setCurrentDialog(null); eventManager.emitEvent( new RerenderScreen( new RerenderScreen.ScreenPart( start, - new TerminalPosition(start.getColumn() + size.getColumns(), start.getRow() + size.getRows()) + new TerminalPosition( + start.getColumn() + size.getColumns(), + start.getRow() + size.getRows() + ) ) ) ); } } + + private void clear(TerminalPosition start, TerminalSize size) { + 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(); + } + } + } } diff --git a/game/src/main/java/cz/jzitnik/client/game/dialog/OnEnd.java b/game/src/main/java/cz/jzitnik/client/game/dialog/OnEnd.java index f6756c6..531b605 100644 --- a/game/src/main/java/cz/jzitnik/client/game/dialog/OnEnd.java +++ b/game/src/main/java/cz/jzitnik/client/game/dialog/OnEnd.java @@ -4,6 +4,11 @@ import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonSubTypes; import com.fasterxml.jackson.annotation.JsonTypeInfo; +import cz.jzitnik.client.game.GameState; +import cz.jzitnik.client.game.Requirement; + +import java.util.Arrays; +import java.util.Optional; @JsonTypeInfo( use = JsonTypeInfo.Id.NAME, @@ -15,9 +20,11 @@ import com.fasterxml.jackson.annotation.JsonTypeInfo; @JsonSubTypes.Type(value = OnEnd.End.class, name = "end"), }) public interface OnEnd { - record End() implements OnEnd {} + record End() implements OnEnd { + } - record RunCode(Runnable runnable, OnEnd onEnd) implements OnEnd {} // TODO: Serialize + record RunCode(Runnable runnable, OnEnd onEnd) implements OnEnd { + } // TODO: Serialize record Continue(Dialog nextDialog) implements OnEnd { @JsonCreator @@ -32,15 +39,46 @@ public interface OnEnd { this.answers = answers; } - public record Answer(String answer, Dialog dialog) { + public record Answer( + String answer, + Dialog dialog, + Optional requirement + ) { @JsonCreator public Answer( @JsonProperty("answer") String answer, - @JsonProperty("dialog") Dialog dialog + @JsonProperty("dialog") Dialog dialog, + @JsonProperty("requirement") Requirement requirement ) { - this.answer = answer; - this.dialog = dialog; + this(answer, dialog, Optional.ofNullable(requirement)); } + + private boolean isValid(GameState gameState) { + if (requirement.isPresent()) { + Requirement requirement = requirement().get(); + if (requirement.itemType() != null) { + if (Arrays.stream(gameState.getPlayer().getInventory()).noneMatch(item -> { + if (item == null) { + return false; + } + + return item.getType().getItemType().getSimpleName().equals(requirement.itemType()); + })) { + return false; + } + } + + return true; + } + + return true; + } + } + + public Answer[] answers(GameState gameState) { + return Arrays.stream(answers) + .filter(answer -> answer.isValid(gameState)) + .toArray(Answer[]::new); } } } \ No newline at end of file diff --git a/game/src/main/java/cz/jzitnik/client/ui/DialogUI.java b/game/src/main/java/cz/jzitnik/client/ui/DialogUI.java index 6de27b5..27b9c50 100644 --- a/game/src/main/java/cz/jzitnik/client/ui/DialogUI.java +++ b/game/src/main/java/cz/jzitnik/client/ui/DialogUI.java @@ -11,6 +11,7 @@ import cz.jzitnik.client.annotations.ui.UI; import cz.jzitnik.client.events.MouseAction; import cz.jzitnik.client.events.RerenderScreen; import cz.jzitnik.client.events.handlers.DialogEventHandler; +import cz.jzitnik.client.game.GameState; import cz.jzitnik.client.game.dialog.OnEnd; import cz.jzitnik.client.states.DialogState; import cz.jzitnik.client.states.ScreenBuffer; @@ -27,42 +28,57 @@ import static cz.jzitnik.client.events.handlers.DialogEventHandler.*; @UI @Dependency public class DialogUI { + @InjectState private DialogState dialogState; @InjectState private TerminalState terminalState; + @InjectState + private ScreenBuffer screenBuffer; + + @InjectState + private GameState gameState; + @InjectDependency private TextRenderer textRenderer; @InjectDependency private EventManager eventManager; - @InjectState - private ScreenBuffer screenBuffer; - @MouseHandler(MouseHandlerType.CLICK) public boolean handleClick(MouseAction mouseAction) { if (dialogState.getCurrentDialog() == null || dialogState.isRenderInProgress()) { return false; } - TerminalSize size = DialogEventHandler.getSize(textRenderer, dialogState.getCurrentDialog()); - TerminalPosition start = DialogEventHandler.getStart(terminalState.getTerminalScreen().getTerminalSize(), size); + var dialog = dialogState.getCurrentDialog(); + TerminalSize size = DialogEventHandler.getSize(textRenderer, dialog, gameState); + TerminalPosition start = DialogEventHandler.getStart( + terminalState.getTerminalScreen().getTerminalSize(), + size + ); - if (!(dialogState.getCurrentDialog().getOnEnd() instanceof OnEnd.AskQuestion( - OnEnd.AskQuestion.Answer[] answers - ))) { + if (!(dialog.getOnEnd() instanceof OnEnd.AskQuestion askQuestion)) { setHoveredButtonIndex(-1); return false; } + var answers = askQuestion.answers(gameState); + 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( 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)) { @@ -70,9 +86,13 @@ public class DialogUI { 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()); + int buttonsStartY = DialogEventHandler.getYStartButtons(textRenderer, dialog); + TerminalPosition localPosition = new TerminalPosition( + mouseNormalized.getColumn() - start.getColumn(), + mouseNormalized.getRow() - start.getRow() - buttonsStartY + ); + + int buttonsHeight = DialogEventHandler.calculateButtonHeight(dialog, gameState); if (localPosition.getRow() < 0 || localPosition.getRow() >= buttonsHeight) { setHoveredButtonIndex(-1); @@ -82,23 +102,27 @@ public class DialogUI { 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(); - } - } + if (buttonIndex < answers.length + && rest < BUTTON_HEIGHT + && localPosition.getColumn() >= PADDING + && localPosition.getColumn() < size.getColumns() - PADDING) { + + clearDialog(start, size); eventManager.emitEvent( new Event[]{ new RerenderScreen( new RerenderScreen.ScreenPart( start, - new TerminalPosition(start.getColumn() + size.getColumns(), start.getRow() + size.getRows()) + new TerminalPosition( + start.getColumn() + size.getColumns(), + start.getRow() + size.getRows() + ) ) ), - answers[buttonIndex].dialog(), - }); + answers[buttonIndex].dialog() + } + ); return true; } @@ -112,21 +136,32 @@ public class DialogUI { return false; } - TerminalSize size = DialogEventHandler.getSize(textRenderer, dialogState.getCurrentDialog()); - TerminalPosition start = DialogEventHandler.getStart(terminalState.getTerminalScreen().getTerminalSize(), size); + var dialog = dialogState.getCurrentDialog(); + TerminalSize size = DialogEventHandler.getSize(textRenderer, dialog, gameState); + TerminalPosition start = DialogEventHandler.getStart( + terminalState.getTerminalScreen().getTerminalSize(), + size + ); - if (!(dialogState.getCurrentDialog().getOnEnd() instanceof OnEnd.AskQuestion( - OnEnd.AskQuestion.Answer[] answers - ))) { + if (!(dialog.getOnEnd() instanceof OnEnd.AskQuestion askQuestion)) { setHoveredButtonIndex(-1); return false; } + var answers = askQuestion.answers(gameState); + 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( 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)) { @@ -134,9 +169,13 @@ public class DialogUI { 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()); + int buttonsStartY = DialogEventHandler.getYStartButtons(textRenderer, dialog); + TerminalPosition localPosition = new TerminalPosition( + mouseNormalized.getColumn() - start.getColumn(), + mouseNormalized.getRow() - start.getRow() - buttonsStartY + ); + + int buttonsHeight = DialogEventHandler.calculateButtonHeight(dialog, gameState); if (localPosition.getRow() < 0 || localPosition.getRow() >= buttonsHeight) { setHoveredButtonIndex(-1); @@ -146,7 +185,11 @@ public class DialogUI { 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) { + if (buttonIndex < answers.length + && rest < BUTTON_HEIGHT + && localPosition.getColumn() >= PADDING + && localPosition.getColumn() < size.getColumns() - PADDING) { + setHoveredButtonIndex(buttonIndex); return true; } @@ -161,4 +204,12 @@ public class DialogUI { eventManager.emitEvent(dialogState.getCurrentDialog()); } } + + private void clearDialog(TerminalPosition start, TerminalSize size) { + 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(); + } + } + } }