The Ebonheim Chronicle

Development Blog for Chronicles IV: Ebonheim

The fruits of all of my labor with regard to asset editors and combat actions results in it being fairly trivial to add huge numbers of different types of abilities. It feels really good to just pop open the engine, write no code, and throw together a few systems into a new ability.

Here I made a ninja-smoke escape ability in just a minute or two. I greyed out the firey explosion graphic I had made before using the new in-line palette swaps to make a nice smoke explosion and then leveraged existing teleport animations to make a quick and dirty smoke bomb.

Enemies calculate their own FoV and also can't see through smoke just like you can't, so leave some smoke tiles behind and you have a great getaway!

#gamedev #chron4

Wishlist Chronicles IV: Ebonheim! 🌐   🙋‍  

I've been having a time getting doors into the game over the last few weeks. After a failed attempt with the wrong methodology and several refactors I think I have it in a good place now!

Doors are special in ways unlike other actors in the game:

  • They can be attacked and killed (thank Barrels for paving the way for non-creatures actors for that)
  • They can be interacted with in a special user interface way.
  • Other actors can cohabitate the same grid-space as an open door, something previously explicitly disallowed in the engine.
  • And they also block line of sight when closed!

A ton of extra systems and refactors have gone into making these doors work, but a lot of that work is going to benefit the next goal which is other interactable actors like item chests!

#gamedev #chron4

Wishlist Chronicles IV: Ebonheim! 🌐   🙋‍  

I have a test map full of a lot of different kinds of enemies that I boot up to test new features. I also load into it a lot to look for polish and ensure that the enemy behavior and execution are all still working correctly. I have found as time has gone on that I find myself just playing through this area regularly, and just enjoy wasting time playing here.

I wanted to record a nice long gif to show one such run because I think it shows off a ton of the mechanics and starts to paint a realistic picture of the pace of the combat in the game.

#gamedev #chron4

Wishlist Chronicles IV: Ebonheim! 🌐   🙋‍  

I have fully embraced the modern nightmare of containerized web servers and the page you're reading right now is served up by a laptop in my home office. One of the nice things about this is I use File Browser which allows me to very easily upload files to my website from anywhere, and it even has a code editor for html and such!

Having direct access to all of this running on my own machine has empowered me to indulge in silly ideas. The most recent being thought of displaying a live-updating view of how much code I've written for my game.

I am in a weird spot where I think that LoC is a terrible metric for code quality or code complexity whilst simultaneously being absolutely addicted to a big number going up.

I have long had a script for running cloc, a neat full-featured utility to count LoC with a ton of options. The script runs the utility with options designed around ignoring dependencies, generated files, or any other “not lines I wrote myself” and I often enjoy checking in on it after a while to see how much larger the number has gotten.

Sometimes I even want to post that number to social media, but I worry about the optics of that and so I figured why not embrace the silliness and just make a place where anyone can easily see my big number and observe how big it is!

First we need some python that will update a copy of the repository, run cloc, and then send the output of the final line count to a file...

#!/usr/bin/env python3

import os
import subprocess
import json


# Define your repository path and the path to cloc.exe
REPO_PATH = '<path_to_checked_out_repo>'
CLOC_PATH = 'cloc'
OUTPUT_FILE = '<path_to_static_web_files>/cloc_out.txt'


def extract_sum_code(cloc_output):
    """Extract the SUM->code value from cloc JSON output."""
    cloc_data = json.loads(cloc_output)
    sum_code = cloc_data["SUM"]["code"]
    print(f"Found sum code: {sum_code}")
    return sum_code

def update_repo():
    """Pull the latest changes from the git repository."""
    os.chdir(REPO_PATH)
    subprocess.run(['git', 'pull', 'origin', 'master'], check=True)

def get_loc_count():
    """Run cloc.exe and get the lines of code count."""
    result = subprocess.run([CLOC_PATH, 'chron4/', '--exclude-dir=assets,x64', '--exclude-ext=inl,filters,vcxproj,recipe,ini,user,chrep,chf,temp,natvis,x', '--exclude-list-file=cloc-exclude.txt', '--json'], capture_output=True, text=Tru>
    cloc_output = result.stdout
    return cloc_output

def write_loc_to_file(loc_count):
    """Write the lines of code count to a file."""
    with open(OUTPUT_FILE, 'w') as file:
        file.write(str(loc_count))
        print("Wrote to file")

def main():
    try:
        update_repo()
        loc_count = get_loc_count()
        sum_code = extract_sum_code(loc_count)
        write_loc_to_file(sum_code)
    except Exception as e:
        print(f"An error occurred: {e}")


if __name__ == "__main__":
    main()

Next we add our script to cron...

0 0 * * * /usr/bin/python3 /<path-to>/runcloc.py >> /<path-to>/cron.log 2>&1

We sent cloc_out.txt to our static files on our nginx web server so referencing the content on our new website is as easy aaaas....

    <div style="text-align:center;">
       <img style="width:25%;" src="pikachu.gif" />
       <p><b><span id="loc"></span></b> lines of code have been written.</p>
       <p><a href="https://blog.brianna.town">Follow Development Updates</a></p>
       <p><a href="https://brianna.town">Return to Author's HomePage</a></p>
       
    </div>

    <script>
        async function fetchLoc() {
            const response = await fetch('cloc_out.txt');
            let loc = await response.text();
            loc = Number(loc).toLocaleString();
            document.getElementById('loc').textContent = loc;
        }
        fetchLoc();
    </script>

And there you go! Please enjoy the awkwardly-domain-named https://chrongame.com and look forward to a release date when the number is much much larger ♥

Wishlist Chronicles IV: Ebonheim! 🌐   🙋‍  

Today is June 28th which means that two years ago today I decided to try to #gamedev again and made my first commit to a new repo for a new engine attempting (for the fourth time) to make a game with the title “Chronicles IV: Ebonheim

Past Attempts

I thought it might be fun to talk about some pre-2022 project history and show some never-before-seen development gifs!

sEGA

Back in 2015, I started a new game engine with the constraints of being

  • 100% pure C
  • low-dependency
  • emulating EGA graphics cards

In the end I think the most powerful result of that endeavor was reshaping my brain around C. It forced me to learn so much about how code actually works and what is going on and completely revolutionized my coding style and ability.

The EGA emulation came as an idea of trying to create a cool arbitrary limitation on the graphical capabilities because old EGA games is some of the first games I ever played as a kid.

You can access that old engine here!

sEGA Games

The original idea for the engine was a point-and-click adventure title called Borrowed Time (BT) in the repo. I have nothing to show for this except for some scattered design documents but the general premise involved using a pocket watch to traverse over a clockwork Majora's Mask -style slice of time and solve a murder (still waiting for my check from the Obra Dinn devs).

By the time the engine was up and running and the graphics all worked I had “shifted” my idea to a turn-based tactical RPG called Shift which involved going on runs by diving into other planes of existence via D&D-style color pools. This project didn't get a lot further but it was influenced a lot by me being into DotA at the time and attempting to come up with new ways to accomplish complex deterministic combat resolution which was a constant pain point in BladeQuest.

BOMBILLAS.BAS

I used sEGA a little later to make a clone of the old QBASIC game GORILLAS.BAS for the Giant Bomb Game Jam!

You can download and play it here and see it being played by the Giant Bomb staff here!!

Chronicles IV (1)

After taking a break, moving apartments, losing a lot of weight, and getting exceptionally into a tabletop game called Burning Wheel, I had the idea of using sEGA to try and make some kind of Burning Wheel, Morrowind, Ultima, completely unrealistic game.

A big part of the pitch was that the whole game world would have (hundreds of) years worth of history mapped out in scripts. You create a starting character whose background would determine their age and starting location. You would then pick and poke and interact with the world to try and cause the course of history to change to accomplish your goals. It was this incredibly ambitious idea of having an RPG character who could literally grow too old and die of natural causes, where learning new skills took months or years of training.

Despite this idea never really coming together or having much hope of turning into anything, it's something I tinkered and played with for three years. It had no ImGui or in-engine edit UI but I still wanted to do all asset editing in-engine. This lead to creating a Lua console and building the map editor into the running instance. It had very tight lua integration for all of the actors. It's honestly wild to me just how much stuff this tech demo did in the end.

Here's some gifs from that project!

Thank you!

To everyone who has been following this project, it has been a joy to share my game's development with you!

Here's to a great third year!!

Wishlist Chronicles IV: Ebonheim! 🌐   🙋‍  

I've been actively developing this game for two years as of this month and have enjoyed keeping a tightly-curated blog of development progress and technical write-ups. But, as that body of work has grown, I've felt less and less easy about having that horse hitched to a platform I can't control or export from.

So now welcome to The Ebonheim Chronicle! All posts and their content from the last two years have been migrated manually here to a laptop in my office at home running Writefreely, a great minimal blog app that also has activitypub federation!

I've added a line of links to the signature of every post with how to access the RSS feed or follow the blog on mastodon or your federated feed of choice!

Wishlist Chronicles IV: Ebonheim! 🌐   🙋‍  

Spent this week building the art and UI around items and equipment. Items are the key to progression in the game! I can't wait to have them actually start affecting combat in meaningful ways 😁

#gamedev #chron4 #pixelart

Wishlist Chronicles IV: Ebonheim! 🌐   🙋‍  

In the combat demo, I had created a system called “Dodge Locking” described by the game's instruction screen as


The idea went something like this:

  • If you go before someone in the turn order you can always just move away from an incoming melee attack, dodging it.
  • Going first should be an advantage but there should still be a cost associated with disengaging from melee range
  • So rather than taking the hits, one or more lockers causes a 1-damage “dodge cost” for disengaging

Here's an example of disengaging from two lockers:

But there were issues with this when playtesting:

  • Few testers understood this mechanic, the closest being individuals asking if it's like Attack of Opportunity in D&D
  • There's already a problem with that demo where kiting enemies to get stamina back is overpowered
  • The dodge cost isn't high enough to make much impact

I worry because I think as a game designer you should trust your weird ideas most of the time and should try to never change something for the purpose of “Will people understand that” Sometimes teaching someone to understand your weird thing can be very impactful! But I did also feel like this system wasn't going to work out in the long run.

So I thought why not just turn it into Attacks of Opportunity! Essentially, if you're locked by an attack or ability, that attack will play out normally in reaction to you moving away. Here it is working:

I believe that this is an overall improvement for a few reasons:

  • Taking the attack that was declared as a cost to moving away makes disengaging much harder and makes melee much more dangerous
  • There's still room to modify this system via passive abilities that can modify the damage, create dodging effects, or adding counters
  • Adding locking to powerful ranged abilities is on the table now too with the same UI messaging

Finally, I am so proud that making this change was just a few lines of code! The combat execution is so modular and structured so nicely that making this enormous change was trivial and automatically works with enemy behavior and preview UI!

#gamedev #chron4

Wishlist Chronicles IV: Ebonheim! 🌐   🙋‍  

I've always wanted to nick this design ever since playing Divinity Original Sin 2. Tiles that accept surface elements can now be made wet, frozen, shrouded, oiled, or burning. Applying elements interact with the existing status in a (mostly) intuitive way: Fire melts ice, water puts fires out, oil burns, etc. etc. Since pushing is a big part of the game's combat, pushing an enemy through fire can be an extremely powerful tool. And anyone moving or getting pushed onto an ice tile will cause them to slide until they hit something! Burning tiles also emit light and smoke from doused fires blocks vision for a few turns before dissipating. The great thing about the enemy behavior system is that it has a very generic function for calculating value and cost from resultant game states. So the high-value things like getting close to an enemy or avoiding damage automatically incorporate the tile hazards and I'm already seeing enemies be smart about ice sliding usage. There's so much to be done with all of this, I can't wait to start using it all with some encounter design and create synergies with different abilities ♥ #gamedev #chron4

Wishlist Chronicles IV: Ebonheim! 🌐   🙋‍  

Light and Vision are critical components for Chronicles with the dark areas of the world requiring torches to light your way.

Additionally, your character will remember tiles you've seen which appear on the screen like a map when they're not currently visible.

This knowledge persists between runs! It's an iterative effort from dozens of runs to map the world 😄

Wishlist Chronicles IV: Ebonheim! 🌐   🙋‍