-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
c891996
commit 3bb22e1
Showing
146 changed files
with
8,028 additions
and
511 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,110 @@ | ||
# Procedural Generation Project 1 | ||
|
||
This project was my first attempt to procedurally generate, well, anything really. I didn't know anything about this | ||
topic but ended up with an old-school text-based adventure game world where the player can travel between locations, | ||
interact with non-player characters, and engage in combat. | ||
|
||
### Features | ||
|
||
#### Procedural generation | ||
|
||
- All objects below are generated procedurally, using a seeded `Random` object and handled by a `WorldHandler` instance | ||
- The core object is `World` which holds `Chunk[][]`, with the player starting in the centre chunk | ||
- Each `Chunk` holds an `int[][]`: | ||
- Based on `int density`, a number of `Location`s are placed in the `Chunk` | ||
- `Location`s are connected using various, configurable strategies which result in the `WorldHandler` | ||
s `Graph<Location>` | ||
- A `Location` (interface) contains reference to neighbouring locations, points of interest inside it, and its location | ||
within the chunk and world | ||
- The only `Location` available at this stage is a `Settlement` | ||
- For generation, the most important feature of a `Location` is its `Size` which determines the `PointOfInterest` | ||
count and, for a `Settlement`, also the `Inhabitant` count | ||
- Each `Location` holds a `List<PointOfInterest>` which the player can visit | ||
- The key object a player interacts with/within is a `PointOfInterest` (**POI**): | ||
- A POIs features are determined by its `Type` | ||
- Currently implemented are the following `Type`s: `ENTRANCE`, `MAIN_SQUARE`, `SHOP`, `QUEST_LOCATION` and `DUNGEON` | ||
- At a POI, the player can engage in dialogues with non-player characters (**NPC**) other events (e.g. "delivery" | ||
or "kill" quests), engage in combat, or take other actions | ||
- Each object of each layer (i.e. `World`, `Chunk`, `Location` and `PointOfInterest`) can be located using `Coordinate` | ||
- The web of connections and the distance between each (both of which stored in the `WorldHandler`s `Graph<Location>`) | ||
play an important role e.g. where a player can travel to and how long it takes | ||
|
||
#### Player loop | ||
|
||
- When playing the game via a CLI, the `Player`'s `State` determines the player loop that is being executed | ||
- Each state (e.g. `PoiLoop` or `CombatLoop`) inherits from `AbstractLoop` | ||
- A loop follows a simple sequence such as this example from the `DialogueLoop`: | ||
|
||
```java | ||
public class DialogueLoop extends AbstractLoop { | ||
|
||
@Override | ||
public void execute(List<Action> actions) { | ||
printInteraction(); // Shows the current `Interaction` from the NPCs `Dialogue` | ||
prepareActions(actions); // Reads the available `Action`s for current `Dialogue` from `Event` | ||
promptPlayer(actions); // Shows the above as a `List<Action>` & executes the selection `Action` | ||
postProcess(); // Handles side effects of the outcome e.g. updating the `Event` | ||
} | ||
} | ||
|
||
``` | ||
|
||
- Almost every loop will `prepareActions()` and `promptPlayer()` | ||
- The `Action` interface is the way through which the `Player` affects the world - examples: | ||
- Move to a different `Location` using `LocationAction` | ||
- Move to a different `PointOfInterest` using `PoiAction` | ||
- Start with an `EventAction` | ||
- Changing the `Player`s `State` using `StateAction` | ||
|
||
#### Other technical features | ||
|
||
- Folder scanning (`FolderReader`) to read available event file names by category (e.g. events) which works inside a JAR | ||
and when running via an IDE | ||
- Processing of Yaml files (`YamlReader`) which allows customising event `Participant`s based on their role ( | ||
e.g. `eventGiver` or `eventTarget`), `List<Dialogue>` for each based on event status, `List<Reward>`, etc. | ||
- Processing of Txt files (`TxtReader`) which is used to generate `Location`, `PointOfInterest` and `Npc` names | ||
- Processing of `String` placeholders in Yaml or Txt files (`PlaceholderProcessor`) e.g. `&L` for location name | ||
or `&TOF` for the target owner's first name through which events are tailored to the current places and characters | ||
involved | ||
|
||
### Technologies used | ||
|
||
- Core: Java 19, Spring Boot 3 + Lombok, Gradle | ||
- `guava` for String manipulation | ||
- `snakeyaml` for Yaml processing | ||
- `google-java-format` for formatting | ||
|
||
### Other notes | ||
|
||
#### More documentation | ||
|
||
- [How to create event YAML files](docs/HOW_TO_YAML_EVENTS.md) | ||
|
||
#### Random ideas for next steps | ||
|
||
- **(User interface)**: Implement Restful API and a web interface as alternative for CLI-based player loop | ||
- **(Procedural generation)**: Implement biomes which: | ||
- Determine difficulty of events, attitude towards player, etc. based on environmental factors | ||
- Determine object characteristics such as names and types of events | ||
- **(Game design)**: Come up with a key objective for the player and an actual (i.e. fun) game loop | ||
- **(Game design)**: Create more `Location` types such as `Castle` | ||
- **(Game design)**: Create more amenities (`PointOfInterest`) with specific functions i.e. shops | ||
- **(Game design)**: Implement player equipment, inventory, and item drops | ||
- **(Game design)**: Implement a trade/currency system and the ability to buy/sell equipment | ||
|
||
### Notes | ||
|
||
#### Formatter | ||
|
||
This project uses `google-java-format`. See https://github.com/google/google-java-format for more details on how to set | ||
it up and use it. | ||
|
||
#### How to run JAR | ||
|
||
```shell | ||
cd build\libs | ||
java -jar -D"spring.profiles.active"=cli-prod procedural_generation_1-0.1.jar | ||
``` | ||
|
||
Alternatively, in IntelliJ create new run configuration with path to JAR and with VM | ||
options `-Dspring.profiles.active=cli-prod`. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
# How to create event YAML files | ||
|
||
This document describes how to create event YAML files. The project uses YAML files to define the content for events. | ||
YAML files stored in `/src/main/resources/events` are read by `YamlReader` and loaded at runtime by the application. | ||
|
||
## Structure | ||
|
||
YAML files are read to `EventDto` objects. They require the following elements: | ||
|
||
1. `eventDetails` -> `EventDetails` | ||
2. `participantData` -> `Map<Role, List<Dialogue>>` | ||
|
||
### Event details | ||
|
||
The `eventDetails` element must contain the `eventType` which is parsed to `EventType` enum (see `Event` class). | ||
All other fields of the class are optional. An `id` is generated automatically and should not be provided. | ||
|
||
```yaml | ||
eventDetails: | ||
eventType: REACH # Event.Type enum | ||
aboutGiver: a delivery # Optional, type String | ||
aboutTarget: the parcel of &O # Optional, type String | ||
rewards: # Optional, type List<Reward> | ||
- type: GOLD # Reward.Type enum | ||
minValue: 2 | ||
maxValue: 15 | ||
``` | ||
### Participant data | ||
The `participantData` element must contain a `Map` of `Role` to `List<Dialogue>`. The `Role` is parsed to `Role` enum ( | ||
see `Role` class). | ||
|
||
Example: | ||
|
||
```yaml | ||
participantData: | ||
EVENT_GIVER: # Role enum | ||
- !dialogue # Indicate mapping to Dialogue class | ||
state: AVAILABLE # Event.State enum | ||
interactions: # List<Interaction> class | ||
- text: Hello! # An interaction; type String | ||
i: 0 # Optional, NOT parsed; type int; interaction number to make it easier to link actions to interactions | ||
- text: How are you? | ||
i: 1 | ||
actions: | ||
- !action # Indicate mapping to Action class | ||
name: (Accept) Alright, I'll do it # Action name, type String | ||
eventState: NONE # Event.State enum | ||
nextInteraction: 1 # The next interaction to display after selection; | ||
# this example will lead to an infinite loop | ||
``` | ||
|
||
### Actions | ||
|
||
- The dialogue of an event can only be exited through an action | ||
- An action can change the state of the event (`eventState`) e.g. from `AVAILABLE` to `COMPLETED` | ||
- An action can also change the state of the player (`playerState`) e.g. from `IN_DIALOGUE` to `AT_POI`, marking the end | ||
of the dialogue | ||
- Both `eventState` and `playerState` can be changed in a single action | ||
- If an action should only advance the dialogue to a different interaction, you must set `eventState` to `NONE` and set | ||
`nextInteraction` to the index of the next interaction | ||
|
||
### Examples | ||
|
||
#### How to create a simple dialogue | ||
|
||
```yaml | ||
eventDetails: | ||
eventType: DIALOGUE | ||
# Add Event.Type DIALOGUE enum | ||
# ...and skip all other eventDetails | ||
participantDetails: | ||
EVENT_GIVER: | ||
- ... | ||
``` | ||
|
||
#### How to branch a dialogue | ||
|
||
```yaml | ||
- !dialogue | ||
state: AVAILABLE | ||
interactions: | ||
- text: Can you do this for me? | ||
i: 0 | ||
actions: # Add an action | ||
- !action # Indicate mapping to Action class | ||
name: (Accept) Alright, I'll do it | ||
eventState: NONE # Set eventState to NONE | ||
nextInteraction: 1 # Set nextInteraction to anything you want | ||
- !action | ||
name: (Decline) I'm sorry, I can't | ||
eventState: NONE | ||
nextInteraction: 2 | ||
- text: Great, thank you! | ||
i: 1 | ||
- text: That's a shame! | ||
i: 2 | ||
``` | ||
|
||
Note that in the example above, "That's a shame!" would be displayed right after "Great, thank you!". When doing | ||
branching like this, make sure to use additional actions to change the event state or end the dialogue to avoid this. | ||
|
||
#### How to end a dialogue | ||
|
||
```yaml | ||
- !dialogue | ||
state: DECLINED | ||
interactions: | ||
- text: Alright, see you around. | ||
actions: # Add an action | ||
- !action # Indicate mapping to Action class | ||
name: Goodbye! # Give it any text/name | ||
playerState: AT_POI # Set playerState to AT_POI | ||
``` | ||
|
||
## Other notes | ||
|
||
Due to the scope and purpose of this project, there is currently no pre-processing of the YAML files. This means that | ||
the YAML files are read when they are assigned to an NPC. This means that any errors in the YAML files will only be | ||
detected at that point. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1 @@ | ||
rootProject.name = 'king_of_castrop_rauxel' | ||
rootProject.name = 'procedural_generation_1' |
18 changes: 12 additions & 6 deletions
18
src/main/java/com/hindsight/king_of_castrop_rauxel/Application.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,18 +1,24 @@ | ||
package com.hindsight.king_of_castrop_rauxel; | ||
|
||
import com.hindsight.king_of_castrop_rauxel.cli.NewGame; | ||
import com.hindsight.king_of_castrop_rauxel.cli.CliGame; | ||
import com.hindsight.king_of_castrop_rauxel.configuration.AppProperties; | ||
import lombok.extern.slf4j.Slf4j; | ||
import org.fusesource.jansi.AnsiConsole; | ||
import org.springframework.boot.SpringApplication; | ||
import org.springframework.boot.autoconfigure.SpringBootApplication; | ||
import org.springframework.boot.context.properties.EnableConfigurationProperties; | ||
|
||
@Slf4j | ||
@SpringBootApplication | ||
@EnableConfigurationProperties(AppProperties.class) | ||
public class Application { | ||
|
||
public static void main(String[] args) { | ||
SpringApplication.run(Application.class, args); | ||
var newGame = new NewGame(); | ||
newGame.start(); | ||
public static void main(String[] args) { | ||
var context = SpringApplication.run(Application.class, args); | ||
if (Boolean.TRUE.equals(AppProperties.getIsRunningAsJar())) { | ||
AnsiConsole.systemInstall(); | ||
} | ||
|
||
var newGame = context.getBean(CliGame.class); | ||
newGame.play(); | ||
} | ||
} |
28 changes: 27 additions & 1 deletion
28
src/main/java/com/hindsight/king_of_castrop_rauxel/action/Action.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,32 @@ | ||
package com.hindsight.king_of_castrop_rauxel.action; | ||
|
||
import static com.hindsight.king_of_castrop_rauxel.characters.Player.*; | ||
import static com.hindsight.king_of_castrop_rauxel.cli.CliComponent.*; | ||
|
||
import com.hindsight.king_of_castrop_rauxel.characters.Player; | ||
|
||
public interface Action { | ||
|
||
int getIndex(); | ||
|
||
void setIndex(int index); | ||
|
||
String getName(); | ||
int getNumber(); | ||
|
||
void setName(String name); | ||
|
||
State getNextState(); | ||
|
||
default void execute(Player player) { | ||
nextState(player); | ||
} | ||
|
||
default void nextState(Player player) { | ||
player.setState(getNextState()); | ||
} | ||
|
||
default String print() { | ||
return "%s[%s%s%s]%s %s" | ||
.formatted(FMT.WHITE, FMT.CYAN, getIndex(), FMT.WHITE, FMT.RESET, getName()); | ||
} | ||
} |
Oops, something went wrong.