From 35918ac0ad2d3e9d2db9f26c7bc6d7d620e1ecce Mon Sep 17 00:00:00 2001 From: jzitnik-dev Date: Sun, 8 Feb 2026 13:25:37 +0100 Subject: [PATCH] feat: Make Dialog serializable --- .../events/handlers/DialogEventHandler.java | 32 +- .../cz/jzitnik/client/game/dialog/Dialog.java | 17 +- .../cz/jzitnik/client/game/dialog/OnEnd.java | 35 +- .../jzitnik/client/game/mobs/DialogMob.java | 20 +- game/src/main/resources/setup/rooms.yaml | 481 ++++++++++++------ 5 files changed, 395 insertions(+), 190 deletions(-) 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 9ed1700..2a66b69 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 @@ -170,31 +170,35 @@ 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)) { - Thread.sleep(1000); 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 (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); - } + 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(); + } + } + + dialogState.setCurrentDialog(null); + eventManager.emitEvent( + new RerenderScreen( + new RerenderScreen.ScreenPart( + start, + new TerminalPosition(start.getColumn() + size.getColumns(), start.getRow() + size.getRows()) + ) + ) + ); } } } diff --git a/game/src/main/java/cz/jzitnik/client/game/dialog/Dialog.java b/game/src/main/java/cz/jzitnik/client/game/dialog/Dialog.java index b2da3b0..7164c2d 100644 --- a/game/src/main/java/cz/jzitnik/client/game/dialog/Dialog.java +++ b/game/src/main/java/cz/jzitnik/client/game/dialog/Dialog.java @@ -1,18 +1,25 @@ package cz.jzitnik.client.game.dialog; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; import cz.jzitnik.client.utils.events.Event; -import lombok.AllArgsConstructor; import lombok.Getter; -import lombok.RequiredArgsConstructor; -@AllArgsConstructor -@RequiredArgsConstructor @Getter public class Dialog implements Event { /** * Characters per second */ - private int typingSpeed = 10; + private final int typingSpeed = 10; private final String text; private final OnEnd onEnd; + + @JsonCreator + public Dialog( + @JsonProperty("text") String text, + @JsonProperty("onEnd") OnEnd onEnd + ) { + this.text = text; + this.onEnd = onEnd; + } } \ No newline at end of file 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 efa2a77..f6756c6 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 @@ -1,13 +1,46 @@ package cz.jzitnik.client.game.dialog; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonTypeInfo; + +@JsonTypeInfo( + use = JsonTypeInfo.Id.NAME, + property = "type" +) +@JsonSubTypes({ + @JsonSubTypes.Type(value = OnEnd.Continue.class, name = "continue"), + @JsonSubTypes.Type(value = OnEnd.AskQuestion.class, name = "ask_question"), + @JsonSubTypes.Type(value = OnEnd.End.class, name = "end"), +}) public interface OnEnd { - record RunCode(Runnable runnable, OnEnd onEnd) implements OnEnd {} + record End() implements OnEnd {} + + record RunCode(Runnable runnable, OnEnd onEnd) implements OnEnd {} // TODO: Serialize record Continue(Dialog nextDialog) implements OnEnd { + @JsonCreator + public Continue(@JsonProperty("nextDialog") Dialog nextDialog) { + this.nextDialog = nextDialog; + } } record AskQuestion(Answer[] answers) implements OnEnd { + @JsonCreator + public AskQuestion(@JsonProperty("answers") Answer[] answers) { + this.answers = answers; + } + public record Answer(String answer, Dialog dialog) { + @JsonCreator + public Answer( + @JsonProperty("answer") String answer, + @JsonProperty("dialog") Dialog dialog + ) { + this.answer = answer; + this.dialog = dialog; + } } } } \ No newline at end of file diff --git a/game/src/main/java/cz/jzitnik/client/game/mobs/DialogMob.java b/game/src/main/java/cz/jzitnik/client/game/mobs/DialogMob.java index 308904f..bc389b4 100644 --- a/game/src/main/java/cz/jzitnik/client/game/mobs/DialogMob.java +++ b/game/src/main/java/cz/jzitnik/client/game/mobs/DialogMob.java @@ -1,7 +1,11 @@ package cz.jzitnik.client.game.mobs; +import com.fasterxml.jackson.annotation.JacksonInject; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; import cz.jzitnik.client.annotations.injectors.InjectDependency; import cz.jzitnik.client.annotations.injectors.InjectState; +import cz.jzitnik.client.game.ResourceManager; import cz.jzitnik.common.models.coordinates.RoomPart; import cz.jzitnik.client.game.dialog.Dialog; import cz.jzitnik.client.game.mobs.tasks.MobRoomTask; @@ -10,14 +14,20 @@ import cz.jzitnik.client.states.DialogState; import cz.jzitnik.client.utils.events.EventManager; import lombok.extern.slf4j.Slf4j; -import java.awt.image.BufferedImage; - @Slf4j -public abstract class DialogMob extends Mob { +public class DialogMob extends Mob { protected Dialog dialog; - public DialogMob(BufferedImage texture, MobRoomTask[] tasks, RoomCords cords, RoomPart collider, Dialog dialog) { - super(texture, tasks, cords, collider); + @JsonCreator + public DialogMob( + @JsonProperty("texture") ResourceManager.Resource texture, + @JsonProperty("tasks") MobRoomTask[] tasks, + @JsonProperty("cords") RoomCords cords, + @JsonProperty("collider") RoomPart collider, + @JsonProperty("dialog") Dialog dialog, + @JacksonInject ResourceManager resourceManager + ) { + super(resourceManager.getResource(texture), tasks, cords, collider); this.dialog = dialog; } diff --git a/game/src/main/resources/setup/rooms.yaml b/game/src/main/resources/setup/rooms.yaml index b9ea51f..836595c 100644 --- a/game/src/main/resources/setup/rooms.yaml +++ b/game/src/main/resources/setup/rooms.yaml @@ -1,216 +1,367 @@ +# ========================= +# START ROOM +# Intro NPC explains the goal + starter chest +# ========================= - id: "spawn" texture: "ROOM1" - #mobs: - # - type: "hittable_drops" - # texture: "PLAYER_FRONT" - # cords: - # x: 100 - # y: 100 - # collider: - # start: - # x: 0 - # y: 52 - # end: - # x: 44 - # y: 78 - # health: 10 - # itemsDrops: - # - name: "Apple" - # type: - # name: "food" - # addHealth: 1 - # texture: "APPLE" - # tasks: - # - type: "blind_following_player" - # speed: 1 - # updateRateMs: 100 - # - type: "attacking_player" - # damage: 5 - # reach: 15 - # updateRateMs: 500 - west: "empty" + mobs: + - type: "dialog" + texture: "OLD_MAN" + cords: { x: 90, y: 90 } + collider: + start: { x: 0, y: 52 } + end: { x: 44, y: 78 } + dialog: + text: "You fell down here too? This cave is cursed..." + onEnd: + type: continue + nextDialog: + text: "The only way out is guarded by a beast deep inside." + onEnd: + type: continue + nextDialog: + text: "Kill it. Bring its skin to the Key Keeper." + onEnd: + type: continue + nextDialog: + text: "He will give you the key to the final gate." + onEnd: { type: end } + + objects: + - objectType: "chest" + cords: { x: 120, y: 50 } + items: + - id: 100 + name: "Rusty Sword" + type: { name: "weapon_sword", dealDamage: 2 } + texture: "RUSTY_SWORD" + - id: 101 + name: "Apple" + type: { name: "food", addHealth: 2 } + texture: "APPLE" + + west: "empty_a" east: "truhlaright" - north: null + north: "filler_entrance_north" south: null + + +# ========================= +# LOOT ROOM 1 +# ========================= - id: "truhlaright" texture: "ROOM1" objects: - objectType: "chest" - cords: - x: 100 - y: 45 + cords: { x: 100, y: 45 } items: - id: 1 - name: "Wooden sword" - type: - name: "weapon_sword" - dealDamage: 1 - texture: "WOODEN_SWORD" + name: "Dagger" + type: { name: "weapon_dagger", dealDamage: 2, attackCooldownMs: 300 } + texture: "DAGGER" - id: 2 - name: "Apple" - type: - name: "food" - addHealth: 1 - texture: "APPLE" + name: "Bread" + type: { name: "food", addHealth: 3 } + texture: "BREAD" + - id: 9 + name: "Shiny Rock" + type: { name: "junk" } + texture: "ROCK" colliders: - - start: - x: 100 - y: 45 - end: - x: 140 - y: 67 + - start: { x: 100, y: 45 } + end: { x: 140, y: 67 } west: "spawn" - east: null + east: "filler_1" north: "klicnik" - south: null + south: "filler_south_1" -- id: "empty" + + +# ========================= +# KEY KEEPER (QUEST NPC) +# ========================= +- id: "klicnik" texture: "ROOM1" - west: null - east: "spawn" - north: "truhlatop" - south: null + mobs: + - type: "dialog" + texture: "KEY_KEEPER" + cords: { x: 100, y: 100 } + collider: + start: { x: 0, y: 52 } + end: { x: 44, y: 78 } + dialog: + text: "Want to leave? Bring me the beast's skin." + onEnd: + type: ask_question + answers: + - answer: "I have it" + requirement: + item: "quest_item_boss_skin" + dialog: + text: "Well done. Here is the key." + giveItem: + id: 300 + name: "Cave Exit Key" + type: { name: "quest_item_final_key" } + texture: "KEY" + onEnd: { type: end } + - answer: "Not yet" + dialog: + text: "Then go back before it finds you." + onEnd: { type: end } -- id: "truhlatop" - texture: "ROOM1" - objects: - - objectType: "chest" - cords: - x: 100 - y: 45 - items: - - id: 3 - name: "Wooden sword" - type: - name: "weapon_sword" - dealDamage: 1 - texture: "WOODEN_SWORD" - - id: 4 - name: "Apple" - type: - name: "food" - addHealth: 1 - texture: "APPLE" - colliders: - - start: - x: 100 - y: 45 - end: - x: 140 - y: 67 - west: "empty2" - east: null - north: null - south: "empty" + west: "filler_k_west" + east: "truhlarightright" + north: "final_room" + south: "truhlaright" -- id: "empty2" - texture: "ROOM1" - west: null - east: "truhlatop" - north: "boss" - south: null + +# ========================= +# BOSS ROOM +# ========================= - id: "boss" texture: "ROOM1" mobs: - type: "hittable_drops" - texture: "PLAYER_FRONT" - cords: - x: 100 - y: 100 + texture: "CAVE_BEAST" + cords: { x: 100, y: 100 } collider: - start: - x: 0 - y: 52 - end: - x: 44 - y: 78 - health: 10 + start: { x: 0, y: 52 } + end: { x: 44, y: 78 } + health: 40 itemsDrops: - - id: 5 - name: "Apple" - type: - name: "food" - addHealth: 1 - texture: "APPLE" + - id: 200 + name: "Beast Skin" + type: { name: "quest_item_boss_skin" } + texture: "BOSS_SKIN" tasks: - type: "following_player" - speed: 1 - updateRateMs: 100 + speed: 2 + updateRateMs: 80 - type: "attacking_player" - damage: 20 - reach: 15 - updateRateMs: 500 + damage: 8 + reach: 18 + updateRateMs: 400 + west: "filler_boss_west" + east: null + north: null + south: "empty_c" + + + +# ========================= +# FINAL ROOM / EXIT +# ========================= +- id: "final_room" + texture: "ROOM_EXIT" + requirement: + item: "quest_item_final_key" + objects: + - objectType: "exit" + cords: { x: 140, y: 40 } west: null east: null north: null - south: "empty2" + south: "klicnik" -- id: "klicnik" + + +# ========================= +# COMBAT FILLER A (zombie) +# ========================= +- id: "empty_a" texture: "ROOM1" mobs: - type: "hittable_drops" - texture: "PLAYER_FRONT" - cords: - x: 100 - y: 100 + texture: "ZOMBIE" + cords: { x: 110, y: 100 } collider: - start: - x: 0 - y: 52 - end: - x: 44 - y: 78 - health: 10 - itemsDrops: - - id: 6 - name: "Apple" - type: - name: "food" - addHealth: 1 - texture: "APPLE" + start: { x: 0, y: 52 } + end: { x: 44, y: 78 } + health: 6 + tasks: + - type: "following_player" + speed: 1 + updateRateMs: 120 + - type: "attacking_player" + damage: 2 + reach: 15 + updateRateMs: 700 + west: "filler_2" + east: "spawn" + north: null + south: "filler_deadend_1" + + + +# ========================= +# COMBAT FILLER B (blind hunter) +# ========================= +- id: "empty_c" + texture: "ROOM1" + mobs: + - type: "hittable_drops" + texture: "BLIND_HUNTER" + cords: { x: 100, y: 100 } + collider: + start: { x: 0, y: 52 } + end: { x: 44, y: 78 } + health: 20 tasks: - type: "blind_following_player" speed: 1 updateRateMs: 100 - type: "attacking_player" - damage: 5 + damage: 6 reach: 15 updateRateMs: 500 - west: null - east: "truhlarightright" - north: null - south: "truhlaright" + west: "filler_c_west" + east: null + north: "boss" + south: null + + +# ========================= +# LOOT ROOM 2 +# ========================= - id: "truhlarightright" texture: "ROOM1" + objects: + - objectType: "chest" + cords: { x: 100, y: 45 } + items: + - id: 7 + name: "Axe" + type: { name: "weapon_axe", dealDamage: 4, attackCooldownMs: 800 } + texture: "AXE" + - id: 8 + name: "Apple" + type: { name: "food", addHealth: 2 } + texture: "APPLE" + colliders: + - start: { x: 100, y: 45 } + end: { x: 140, y: 67 } west: "klicnik" + east: "filler_rr_east" + north: null + south: null + + + +# ========================= +# EXTRA FILLER ROOMS (maze / dead ends) +# ========================= +- id: "filler_1" + texture: "ROOM1" + west: "truhlaright" + east: "filler_1b" + north: null + south: null + +- id: "filler_1b" + texture: "ROOM1" + west: "filler_1" + east: null + north: null + south: null + +- id: "filler_2" + texture: "ROOM1" + west: null + east: "empty_a" + north: "filler_loop_1" + south: null + +- id: "filler_loop_1" + texture: "ROOM1" + west: null + east: null + north: "filler_loop_2" + south: "filler_2" + +- id: "filler_loop_2" + texture: "ROOM1" + west: "filler_loop_3" + east: null + north: null + south: "filler_loop_1" + +- id: "filler_loop_3" + texture: "ROOM1" + west: null + east: "filler_loop_2" + north: null + south: null + +- id: "filler_deadend_1" + texture: "ROOM1" + west: null + east: null + north: "empty_a" + south: null + +- id: "filler_k_west" + texture: "ROOM1" + west: "filler_k_west_2" + east: "klicnik" + north: null + south: null + +- id: "filler_k_west_2" + texture: "ROOM1" + west: null + east: "filler_k_west" + north: null + south: null + +- id: "filler_boss_west" + texture: "ROOM1" + west: null + east: "boss" + north: null + south: null + +- id: "filler_c_west" + texture: "ROOM1" + west: null + east: "empty_c" + north: null + south: null + +- id: "filler_rr_east" + texture: "ROOM1" + west: "truhlarightright" + east: null + north: null + south: null + +- id: "filler_south_1" + texture: "ROOM1" + west: null + east: null + north: "truhlaright" + south: "filler_south_2" + +- id: "filler_south_2" + texture: "ROOM1" + west: null + east: null + north: "filler_south_1" + south: null + +- id: "filler_entrance_north" + texture: "ROOM1" + west: null + east: "filler_north_2" + north: null + south: "spawn" + +- id: "filler_north_2" + texture: "ROOM1" + west: "filler_entrance_north" east: null north: null south: null - objects: - - objectType: "chest" - cords: - x: 100 - y: 45 - items: - - id: 7 - name: "Wooden sword" - type: - name: "weapon_sword" - dealDamage: 1 - texture: "WOODEN_SWORD" - - id: 8 - name: "Apple" - type: - name: "food" - addHealth: 1 - texture: "APPLE" - colliders: - - start: - x: 100 - y: 45 - end: - x: 140 - y: 67