feat: Make Dialog serializable

This commit is contained in:
2026-02-08 13:25:37 +01:00
parent 5366162f43
commit 35918ac0ad
5 changed files with 395 additions and 190 deletions

View File

@@ -170,31 +170,35 @@ public class DialogEventHandler extends AbstractEventHandler<Dialog> {
} }
private void next(OnEnd onEnd, TerminalPosition start, TerminalSize size) throws InterruptedException { private void next(OnEnd onEnd, TerminalPosition start, TerminalSize size) throws InterruptedException {
Thread.sleep(1000);
if (onEnd instanceof OnEnd.Continue(Dialog nextDialog)) { if (onEnd instanceof OnEnd.Continue(Dialog nextDialog)) {
Thread.sleep(1000);
for (int y = start.getRow(); y < start.getRow() + size.getRows(); y++) { for (int y = start.getRow(); y < start.getRow() + size.getRows(); y++) {
for (int x = start.getColumn(); x < start.getColumn() + size.getColumns(); x++) { for (int x = start.getColumn(); x < start.getColumn() + size.getColumns(); x++) {
screenBuffer.getGlobalOverrideBuffer()[y][x] = new Empty(); screenBuffer.getGlobalOverrideBuffer()[y][x] = new Empty();
} }
} }
if (nextDialog == null) { eventManager.emitEvent(nextDialog);
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);
}
} else if (onEnd instanceof OnEnd.RunCode(Runnable runnable, OnEnd end)) { } else if (onEnd instanceof OnEnd.RunCode(Runnable runnable, OnEnd end)) {
dependencyManager.inject(runnable); dependencyManager.inject(runnable);
runnable.run(); runnable.run();
next(end, start, size); 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())
)
)
);
} }
} }
} }

View File

@@ -1,18 +1,25 @@
package cz.jzitnik.client.game.dialog; 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 cz.jzitnik.client.utils.events.Event;
import lombok.AllArgsConstructor;
import lombok.Getter; import lombok.Getter;
import lombok.RequiredArgsConstructor;
@AllArgsConstructor
@RequiredArgsConstructor
@Getter @Getter
public class Dialog implements Event { public class Dialog implements Event {
/** /**
* Characters per second * Characters per second
*/ */
private int typingSpeed = 10; private final int typingSpeed = 10;
private final String text; private final String text;
private final OnEnd onEnd; private final OnEnd onEnd;
@JsonCreator
public Dialog(
@JsonProperty("text") String text,
@JsonProperty("onEnd") OnEnd onEnd
) {
this.text = text;
this.onEnd = onEnd;
}
} }

View File

@@ -1,13 +1,46 @@
package cz.jzitnik.client.game.dialog; 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 { 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 { record Continue(Dialog nextDialog) implements OnEnd {
@JsonCreator
public Continue(@JsonProperty("nextDialog") Dialog nextDialog) {
this.nextDialog = nextDialog;
}
} }
record AskQuestion(Answer[] answers) implements OnEnd { record AskQuestion(Answer[] answers) implements OnEnd {
@JsonCreator
public AskQuestion(@JsonProperty("answers") Answer[] answers) {
this.answers = answers;
}
public record Answer(String answer, Dialog dialog) { public record Answer(String answer, Dialog dialog) {
@JsonCreator
public Answer(
@JsonProperty("answer") String answer,
@JsonProperty("dialog") Dialog dialog
) {
this.answer = answer;
this.dialog = dialog;
}
} }
} }
} }

View File

@@ -1,7 +1,11 @@
package cz.jzitnik.client.game.mobs; 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.InjectDependency;
import cz.jzitnik.client.annotations.injectors.InjectState; import cz.jzitnik.client.annotations.injectors.InjectState;
import cz.jzitnik.client.game.ResourceManager;
import cz.jzitnik.common.models.coordinates.RoomPart; import cz.jzitnik.common.models.coordinates.RoomPart;
import cz.jzitnik.client.game.dialog.Dialog; import cz.jzitnik.client.game.dialog.Dialog;
import cz.jzitnik.client.game.mobs.tasks.MobRoomTask; 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 cz.jzitnik.client.utils.events.EventManager;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import java.awt.image.BufferedImage;
@Slf4j @Slf4j
public abstract class DialogMob extends Mob { public class DialogMob extends Mob {
protected Dialog dialog; protected Dialog dialog;
public DialogMob(BufferedImage texture, MobRoomTask[] tasks, RoomCords cords, RoomPart collider, Dialog dialog) { @JsonCreator
super(texture, tasks, cords, collider); 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; this.dialog = dialog;
} }

View File

@@ -1,216 +1,367 @@
# =========================
# START ROOM
# Intro NPC explains the goal + starter chest
# =========================
- id: "spawn" - id: "spawn"
texture: "ROOM1" texture: "ROOM1"
#mobs: mobs:
# - type: "hittable_drops" - type: "dialog"
# texture: "PLAYER_FRONT" texture: "OLD_MAN"
# cords: cords: { x: 90, y: 90 }
# x: 100 collider:
# y: 100 start: { x: 0, y: 52 }
# collider: end: { x: 44, y: 78 }
# start: dialog:
# x: 0 text: "You fell down here too? This cave is cursed..."
# y: 52 onEnd:
# end: type: continue
# x: 44 nextDialog:
# y: 78 text: "The only way out is guarded by a beast deep inside."
# health: 10 onEnd:
# itemsDrops: type: continue
# - name: "Apple" nextDialog:
# type: text: "Kill it. Bring its skin to the Key Keeper."
# name: "food" onEnd:
# addHealth: 1 type: continue
# texture: "APPLE" nextDialog:
# tasks: text: "He will give you the key to the final gate."
# - type: "blind_following_player" onEnd: { type: end }
# speed: 1
# updateRateMs: 100 objects:
# - type: "attacking_player" - objectType: "chest"
# damage: 5 cords: { x: 120, y: 50 }
# reach: 15 items:
# updateRateMs: 500 - id: 100
west: "empty" 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" east: "truhlaright"
north: null north: "filler_entrance_north"
south: null south: null
# =========================
# LOOT ROOM 1
# =========================
- id: "truhlaright" - id: "truhlaright"
texture: "ROOM1" texture: "ROOM1"
objects: objects:
- objectType: "chest" - objectType: "chest"
cords: cords: { x: 100, y: 45 }
x: 100
y: 45
items: items:
- id: 1 - id: 1
name: "Wooden sword" name: "Dagger"
type: type: { name: "weapon_dagger", dealDamage: 2, attackCooldownMs: 300 }
name: "weapon_sword" texture: "DAGGER"
dealDamage: 1
texture: "WOODEN_SWORD"
- id: 2 - id: 2
name: "Apple" name: "Bread"
type: type: { name: "food", addHealth: 3 }
name: "food" texture: "BREAD"
addHealth: 1 - id: 9
texture: "APPLE" name: "Shiny Rock"
type: { name: "junk" }
texture: "ROCK"
colliders: colliders:
- start: - start: { x: 100, y: 45 }
x: 100 end: { x: 140, y: 67 }
y: 45
end:
x: 140
y: 67
west: "spawn" west: "spawn"
east: null east: "filler_1"
north: "klicnik" north: "klicnik"
south: null south: "filler_south_1"
- id: "empty"
# =========================
# KEY KEEPER (QUEST NPC)
# =========================
- id: "klicnik"
texture: "ROOM1" texture: "ROOM1"
west: null mobs:
east: "spawn" - type: "dialog"
north: "truhlatop" texture: "KEY_KEEPER"
south: null 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" west: "filler_k_west"
texture: "ROOM1" east: "truhlarightright"
objects: north: "final_room"
- objectType: "chest" south: "truhlaright"
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"
- id: "empty2"
texture: "ROOM1"
west: null
east: "truhlatop"
north: "boss"
south: null
# =========================
# BOSS ROOM
# =========================
- id: "boss" - id: "boss"
texture: "ROOM1" texture: "ROOM1"
mobs: mobs:
- type: "hittable_drops" - type: "hittable_drops"
texture: "PLAYER_FRONT" texture: "CAVE_BEAST"
cords: cords: { x: 100, y: 100 }
x: 100
y: 100
collider: collider:
start: start: { x: 0, y: 52 }
x: 0 end: { x: 44, y: 78 }
y: 52 health: 40
end:
x: 44
y: 78
health: 10
itemsDrops: itemsDrops:
- id: 5 - id: 200
name: "Apple" name: "Beast Skin"
type: type: { name: "quest_item_boss_skin" }
name: "food" texture: "BOSS_SKIN"
addHealth: 1
texture: "APPLE"
tasks: tasks:
- type: "following_player" - type: "following_player"
speed: 1 speed: 2
updateRateMs: 100 updateRateMs: 80
- type: "attacking_player" - type: "attacking_player"
damage: 20 damage: 8
reach: 15 reach: 18
updateRateMs: 500 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 west: null
east: null east: null
north: null north: null
south: "empty2" south: "klicnik"
- id: "klicnik"
# =========================
# COMBAT FILLER A (zombie)
# =========================
- id: "empty_a"
texture: "ROOM1" texture: "ROOM1"
mobs: mobs:
- type: "hittable_drops" - type: "hittable_drops"
texture: "PLAYER_FRONT" texture: "ZOMBIE"
cords: cords: { x: 110, y: 100 }
x: 100
y: 100
collider: collider:
start: start: { x: 0, y: 52 }
x: 0 end: { x: 44, y: 78 }
y: 52 health: 6
end: tasks:
x: 44 - type: "following_player"
y: 78 speed: 1
health: 10 updateRateMs: 120
itemsDrops: - type: "attacking_player"
- id: 6 damage: 2
name: "Apple" reach: 15
type: updateRateMs: 700
name: "food" west: "filler_2"
addHealth: 1 east: "spawn"
texture: "APPLE" 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: tasks:
- type: "blind_following_player" - type: "blind_following_player"
speed: 1 speed: 1
updateRateMs: 100 updateRateMs: 100
- type: "attacking_player" - type: "attacking_player"
damage: 5 damage: 6
reach: 15 reach: 15
updateRateMs: 500 updateRateMs: 500
west: null west: "filler_c_west"
east: "truhlarightright" east: null
north: null north: "boss"
south: "truhlaright" south: null
# =========================
# LOOT ROOM 2
# =========================
- id: "truhlarightright" - id: "truhlarightright"
texture: "ROOM1" 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" 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 east: null
north: null north: null
south: 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