From 524b5c15270dc9c684ff9648bffffc914ffe0a25 Mon Sep 17 00:00:00 2001 From: Row Date: Sat, 22 Jun 2024 18:42:46 -0300 Subject: [PATCH] Character examples --- README.md | 2 +- doc/characters.md | 53 +++++++++++++++++-- doc/items.md | 2 +- .../keep/samples/advanced/characters/alice.kt | 26 ++++++--- .../keep/core/entities/characters/dsl.kt | 35 +++++++++--- 5 files changed, 101 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 0a9d966..abf17d8 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,7 @@ Then create a main character, it requires a key (`"player"`) to identify it, and val mainCharacter = mainCharacter("player", "you") ``` -Then create a scene, it requires a key (`"hello-keep""`) to identify it, and a title name to display (`"Hello Keep""`), scenes usually also have a `narration` as third argument to provide context to the player. The last argument is very important, but we won't use it for now so we just use an empty action list using `actions()`. +Then create a scene, it requires a key (`"hello-keep"`) to identify it, and a title name to display (`"Hello Keep""`), scenes usually also have a `narration` as third argument to provide context to the player. The last argument is very important, but we won't use it for now so we just use an empty action list using `actions()`. ```kotlin val scene = Scene("hello-keep", "Hello Keep", "Keep is a text game engine.", actions()) diff --git a/doc/characters.md b/doc/characters.md index fa8aa6a..ade590c 100644 --- a/doc/characters.md +++ b/doc/characters.md @@ -1,5 +1,52 @@ -# Keep Engine +# Keep Engine - Characters -## Characters +## Simple characters -## +Simple characters are declared using the `npc` function with a `key` to identify them, a display `name`, and a `description`. Typically, a callback is registered using `onTalk`, it will be called when the `Talk` action is used on the character. + +The following example shows Bob, who can be talked to by the player and will answer back. + +```kotlin +val bob = + npc( + "bob", + "Bob", + "Bob, an NPC." + ) onTalk { + io.paragraph("${target.name}: Hello ${game.mainCharacter.name}!.") + io.promptContinue() + } +``` + +## Stateful Characters + +Characters can also hold state, allowing them to react to the world and the player's actions. Use an overload of the `npc` function, and declare `key`, `initialState` and `states` parameters. + +Each state has its own `key`, also the state has `name` and `description` properties that replace the character's ones. Each state can have its own subscriptions to `onTalk` or any other actions, allowing the character to change its dialogues and behavior. Name and description are set on each state to make it easy to reflect changes on each character. + +The following example shows Alice, who greets the player by his name only once. + +```kotlin +val alice = + npc( + "alice", + "first-time", + characterState( + "first-time", + "Alice", + "Alice, another NPC.", + ) onTalk { + io.paragraph("${target.name}: Oh, hi ${game.mainCharacter.name}!.") + target.change("regular") + io.promptContinue() + }, + characterState( + "regular", + "Alice", + "Alice, another NPC.", + ) onTalk { + io.paragraph("${target.name}: Hello.") + io.promptContinue() + } + ) +``` diff --git a/doc/items.md b/doc/items.md index 96f5645..628ee20 100644 --- a/doc/items.md +++ b/doc/items.md @@ -22,7 +22,7 @@ val potion = Items can hold state, allowing the world to react and be modified by the player's actions. Use an overload of the `item` function, and declare `key`, `initialState` and `states` parameters. -Each state has its own `key`, also the state have `name` and `description` properties that replace the item ones. Each state can have its own subscriptions to `onUse` and other actions, allowing the item to change its behavior. +Each state has its own `key`, also the state has `name` and `description` properties that replace the item's ones. Each state can have its own subscriptions to `onUse` or any other actions, allowing the item to change its behavior. The following example shows a switch that can be turned on or off by the player. diff --git a/samples/src/main/kotlin/tech/alephia/keep/samples/advanced/characters/alice.kt b/samples/src/main/kotlin/tech/alephia/keep/samples/advanced/characters/alice.kt index 6a016cc..d9b6f2e 100644 --- a/samples/src/main/kotlin/tech/alephia/keep/samples/advanced/characters/alice.kt +++ b/samples/src/main/kotlin/tech/alephia/keep/samples/advanced/characters/alice.kt @@ -1,14 +1,28 @@ package tech.alephia.keep.samples.advanced.characters +import tech.alephia.keep.core.entities.characters.characterState import tech.alephia.keep.core.entities.characters.npc import tech.alephia.keep.core.events.onTalk val alice = npc( "alice", - "Alice", - "Alice, another NPC." - ) onTalk { - io.paragraph("${target.name}: Hi, how have you been?") - io.promptContinue() - } + "first-time", + characterState( + "first-time", + "Alice", + "Alice, another NPC.", + ) onTalk { + io.paragraph("${target.name}: Oh, hi ${game.mainCharacter.name}!.") + target.change("regular") + io.promptContinue() + }, + characterState( + "regular", + "Alice", + "Alice, another NPC.", + ) onTalk { + io.paragraph("${target.name}: Hello.") + io.promptContinue() + } + ) diff --git a/src/main/kotlin/tech/alephia/keep/core/entities/characters/dsl.kt b/src/main/kotlin/tech/alephia/keep/core/entities/characters/dsl.kt index 7ff684c..0f188d9 100644 --- a/src/main/kotlin/tech/alephia/keep/core/entities/characters/dsl.kt +++ b/src/main/kotlin/tech/alephia/keep/core/entities/characters/dsl.kt @@ -1,5 +1,8 @@ package tech.alephia.keep.core.entities.characters +import tech.alephia.keep.core.entities.items.IndefiniteArticle +import tech.alephia.keep.core.entities.items.ItemState +import tech.alephia.keep.core.entities.items.defaultItemSubscriptions import tech.alephia.keep.core.events.Subscriptions import tech.alephia.keep.core.storages.ItemStorage @@ -10,27 +13,47 @@ fun mainCharacter( inventory: ItemStorage = mainCharacterInventory() ) = singleState(name, description) - .let { mainCharacter(key, inventory, it.key, it) } + .let { mainCharacter(key, it.key, inventory, it) } fun mainCharacter( key: String, - inventory: ItemStorage = mainCharacterInventory(), initialState: String, + inventory: ItemStorage = mainCharacterInventory(), vararg states: CharacterState ): Character = SimpleCharacter(key, inventory, initialState, states.toList(), Subscriptions()) -fun npc(key: String, name: String, description: String = "", inventory: ItemStorage = ItemStorage()) = - singleState(name, description).let { npc(key, inventory, it.key, it) } +fun npc( + key: String, + name: String, + description: String = "", + inventory: ItemStorage = ItemStorage() +) = + singleState(name, description) + .let { npc(key, it.key, inventory, it) } + fun npc( key: String, - inventory: ItemStorage = ItemStorage(), initialState: String, vararg states: CharacterState +): Character = + SimpleCharacter(key, ItemStorage(), initialState, states.toList(), Subscriptions()) + +fun npc( + key: String, + initialState: String, + inventory: ItemStorage = ItemStorage(), + vararg states: CharacterState ): Character = SimpleCharacter(key, inventory, initialState, states.toList(), Subscriptions()) +fun characterState( + key: String, + name: String, + description: String = "", +) = CharacterState(key, name, description, Subscriptions()) + private fun mainCharacterInventory() = ItemStorage( listOf(), @@ -40,4 +63,4 @@ private fun mainCharacterInventory() = ) private fun singleState(name: String, description: String) = - CharacterState("single-state", name, description, Subscriptions()) + characterState("single-state", name, description)