The Ebonheim Chronicle

Development Blog for Chronicles IV: Ebonheim

This has been living in the back of my head for weeks and I haven't had the bandwidth to devote to solutions but here's a #longpost about the specifics and some other thoughts.


First off, how do targets and actions work?

Main Article: The Big Complicated Chronicles Actions System
An ability executes a list of Action Tokens in order which compiled from an asset[1]. One type of token can be a Target Request which is a ton of configuration options for what is selectable in that request; range, direction, line of sight, that sort of thing. The two main genres of target request are defined by their decision-type, which is either “Tile-Pick” (ie. pick a tile within 4 tiles of the user) or “Directable” (ie. N, S, E, W)

  1. I say “asset” here because it's not super relevant, you could also call it an action script, but for 99% of cases I'm dealing with now the action script is 1:1 equivalent to the compiled token set.

Other tokens like Damage or Push will then reference the results of a previously-declared target request token.

Why does this make behavior hard?

Calculating AI for “Move/Attack” is trivial because it's a single directional target request and you just plug in the direction of the player, but that's hard-coded. Something like “Blink Strike” may have a token list more like this:

  1. Declare target tar which is any occupied tile within 10 tiles of the user.
  2. Declare target dir which is a range-1 directable empty location directed from the result of tar
  3. Play “blink” animation from origin to dir
  4. Teleport actor at origin to dir
  5. Play “attack” animation from dir to tar
  6. Damage actor at tar

You can read this action set and get an idea of what the ability does but ask yourself, how do you programmatically reason with this ability's function? The token system is granular and abstract enough that extremely complicated multi-stage dependent abilities can be made with it, which is an important part of the variety I want in the abilities in the game!

How does the NPC behavior decision step determine which tile position to plug into tar and what direction to plug into dir??? Why did it even pick Blink Strike in the first place? How did it know it could possibly be in range to use that? How do you even reason that one target will move you but another will damage a target? Maybe you can start to picture some high level solutions here where you need to break down the state of the board to determine what is available but guess what? You cannot know what tiles are available to pick for dir until you simulate the entire turn up until that point! Decisions are made based on the expected board state at the time of the actor acting, and then saved until all decisions are made.

A 3-step targeted ability consisting of a tile-pick within 10 tiles away, followed by a dependant direction, followed by 5-away tile pick gets you to 50 thousand possible decisions, and it explodes from there if you want to do multi turn solving. If each combination requires you to copy the game state to simulate the outcome, well you're sunk!

How do you select what ability to even use?

There's a very large spectrum of how the enemy behavior will ultimately work. The dream was to be able to define “Personality” assets where the actor will have a set of goals and will be able to reason with the abilities they have and their resources and cooldowns to determine correct usages of those abilities to accomplish the goals of themselves or their faction. I still think this is attainable!

There's also a lower-tech approach, which is very simple rules-based priorities. A behavior can be a list of states in priority order. Each state would contain:

  1. A list of conditions to satisfy that are easy to calculate: “further than X tiles away from enemy”, “not in line-of-sight from enemy”, “there's a nearby empty choke-point”, etc.
  2. An explicit ability to use: “move/attack”, “blink strike”
  3. A list of desired outcomes that are easy to calculate: “Target attacked” “User moved closer to target” “User is covering choke point”

To determine the decision on a turn, iterate over the list until the conditions are met, attempt to satisfying the outcomes by solving the possible target combinations of the ability, being unable to satisfy the outcomes counts as a failed condition, move on to next state.

I think the important thing to note about both approaches, one being dynamic ability selection from a personality solver, the other being an explicit rules-based approach, is that both still require a “Fill in the target requests from this ability programmatically” and ultimately that is the actually hard part!

So, regardless of how we got to the answer of “this is the ability we want to use” we still have to optimize the target request decision-making.

Making an ability solvable

Rather than worrying about how to solve all the possible combinations of target decisions, we can just do what A* does, which is adding a heuristic.

If we think back to solving a serious of target requests, the difficult cases are always with the tile-pick choices. On a graph of possible solutions to traverse, a directional choice has up to 4 neighbor nodes, but a tile pick could be any tile within a range and can scale up very quickly. Picking a tile within 5 tiles of the user has 60 neighbors!

Similar to pathfinding on a 2D grid, if all target requests were directional there would be far less concern about combinatorial explosion. And even if my computing power were infinite I would still have a problem where I can tile-pick until a range condition is satisfied, but I can't solve for the best tile-pick (such as closest or furthest).

One solution to this could be actually as simple as tagging the target requests in the ability asset! I could very easily tag a tile-pick target request token with “Prefer closest to/furthest from enemy/ally/choke-point/wall” and tag directional requests with “away from/toward” similarly. This way I could encode the intent of a target request into the ability definition.

Onto the solver, for tile pick requests, I can now sort the potential tiles by how close they are to the tagged reference. I can even choose the closest tile as the new root and only allow up to 4 neighbors from that root. Essentially, by declaring the intent of the tile-pick, I can tightly-constrain the considered neighbors and get it closer to the requirement of directional requests.

In Conclusion

At some point I need to sit down and actually get coding! But I feel a little less lost with this outline and some of my chief concerns are addressed. It's worth a shot now :host-nervous:

Thanks for reading I hope this was interesting! Feedback and criticism are all welcome! :host-love:

#gamedev #chron4 #longpost

| 🌐 | 🙋‍ | @britown@blog.brianna.town

I know I want to implement a fairly simple FSM graph for NPC AI but I'm running into a few issues with the implementation. You want to very simply say things such as “If you can reach the player to use a specific ability on them within X turns, try to do that.”

You want to be able to define these rules-based behavior states in assets and not in code because that's how we're doing things.


Here are the constraints:

  1. Abilities are lists of actions that may request 0-N targets or perform 0-N actions on the target results. These are defined in assets and are completely arbitrary. (You could have a single ability that uses dependent multi-stage targeting that heals, pushes, or damages completely arbitrary sets of actors)
  2. Determining the “Result” of an ability is thus done by executing the actions onto a copy of the game state to simulate it.
  3. Because of these two things it's very hard to reference a specific target request or specific action from within an ability because the it's generally taken as whole.
  4. If I wanted part of the behavior graph to say something like “move into range to use the blink arrow ability on an enemy” that sounds simple but blink arrow actually takes two different targets, the second being the teleport position adjacent to the first target, thus how does the behavior script know what “Use on an enemy” means??

There is a concept of result cache which is data collected during the virtual execution of the ability for drawing UI, so hypothetically you could build a graph of potential movement squares with all the different decisions in a given ability and then dyjsktra's it searching for a path that satisfies a declared set of end-goals “target enemy is damaged” or “target enemy is closer to you than they started”

The issue with this is that hypothetically you would be copying the entire game state in every node of a giant combinatorial explosive behavior graph, which will probably go nuclear and die for performance. But maybe it's worth trying to see!

#gamedev #chron4

| 🌐 | 🙋‍ | @britown@blog.brianna.town

Something I've been putting off for a while. Turns in the game are resolved in a single frame and then the action tokens set up a timeline of animations during execution which play at specific frame counts so that you can watch the turn play out.

This was made to work with actor positions, some of the movement animations, syncing up the camera movement, the palette flashing, all had to be put onto a timeline to deterministically resolve at a given frame.

Now I have a system to add arbitrary rendering to this. Projectile abilities can show a circle flying to the target (left gif), damage numbers can come up as a response to taking damage (right gif), etc, etc. This is the final piece of the puzzle for figuring out what is happening during combat execution :host-joy:

#gamedev #chron4

| 🌐 | 🙋‍ | @britown@blog.brianna.town

A ton of tiny tweaks here to try and make the flow more readable!

#gamedev #chron4

| 🌐 | 🙋‍ | @britown@blog.brianna.town

I had to go through a pretty sizable refactor, dubbed The Big Complicated Chronicles Actions System 2: Multi-Stage Targeting Working Correctly[1] but now I've ironed out all of the kinks with my game state simulation and action compilation!

Here I'm showing off some of the first abilities I designed for the combat puzzles demo. We're still missing some better combat UI and graphical effects making it clear what's happening, but there's some really neat stuff in here!

  1. The sequel to this

#gamedev #chron4

| 🌐 | 🙋‍ | @britown@blog.brianna.town

Abilities now have stamina cost and cooldowns! Buttons gray out when on CD, and the stam works too just hard to tell in this gif. Showing off the force-push and blink abilities working too :host-joy:

Edit: some better looking numbers UI:

#gamedev #chron4

| 🌐 | 🙋‍ | @britown@blog.brianna.town

Really feeling happy with how simple and straightforward it is to add new actions to the combat. This took less than an hour start to finish.

#gamedev #chron4

| 🌐 | 🙋‍ | @britown@blog.brianna.town

Clarifying what's happening with the combat is a constant battle (pun intended). Got around to hooking things up to the Members window to show turn execution along with enemy names and decided action. Also my plan for the combat demo is nearing around 20% complete[1] as I continue to check off items so that's exciting!

  1. Assuming every item on the task list is exactly equal in size and scope lmao

#gamedev #chron4

| 🌐 | 🙋‍ | @britown@blog.brianna.town

All the actions work has been manual to test for a while, so I finally got over the hump of attaching them to Ability assets which attach to Actor assets so an Actor can dynamically give you abilities to use in your hotbar which you can click to select and use!

The ability name also shows a nice UI window in the corner :host-joy:

#gamedev #chron4

| 🌐 | 🙋‍ | @britown@blog.brianna.town