Weekly Update – January 20, 2023

  • Map generation optimization. I knew as I wrote Map Generator 2.0 that some of the code was horribly slow and wasteful and would need to be optimized later. Map generation had ballooned to 10-15 seconds and .5 GB of memory. It’s now down to 3-5 seconds and 200 MB of memory, and there’s much more room for improvement. The optimization techniques were converting LINQ statements to for loops and reducing use of temporary lists. One optimization example involved how connections between rooms are stored. I started with one list that stored all original connections between rooms. Then I created a new list for connections that constructed loops and another new list for connections that joined sections. I used separate lists instead of the original list because I needed to do different things with the items in these lists, and it was more expedient to create new lists (though a little voice inside my head was telling me to slow down and do it the right way). I added a fourth list when I realized I needed to track each connection in each room that used the connection (as opposed to only the room that originated the connection). Because it was sometimes necessary to get all of the connections, I created a property that combined all four lists into one new list. Yikes. The allocations… The solution was to combine the lists into one and add an attribute indicating the type of connection. This caused way more rework, and troubleshooting issues caused by the rework, than I anticipated. At least the rework made the code simpler and easier to understand, which is always beneficial.
  • Movement optimization. Enabling actor actions to be displayed simultaneously exposed a problem: the movement code took a long time to run, causing actors to instantly move to the next cell rather than moving incrementally over multiple frames. Linear interpolation is used to calculate how far an actor moves each frame, with the actor’s movement speed and elapsed time since the last update as inputs. I ran the Unity profiler and identified the main causes: dynamic lighting and excessive Unity log calls. The log calls are easy enough to deal with; they won’t be in production releases. Dynamic lighting, which uses the Smart Lighting 2D asset, is a dilemma. I want to keep it in the game but I’m not sure how much I can optimize it. Temporarily disabling the lighting and logging fixed movement between two cells, but there was still an issue when moving across multiple cells. Actors momentarily stopped at each new cell before moving again. I had seen this before and knew the source: the state logic in the Update method caused some frames to skip movement. For example, an Update call would determine that all actions had finished in the previous Update and it would update the turn state. Movement wouldn’t resume until the next Update. With nested state logic (there are turn phases, actions phases, and action step phases), several frames passed before movement resumed. This was resolved by modifying the state logic to process state changes in the same update when applicable. For example, when an action step finishes, the logic will start the next action step in the same update.
  • Displaying actor actions simultaneously. I reverted the changes I made last week to present actor actions simultaneously. It became clear that an enormous amount of rework was needed to separate action logic and presentation. Fortunately, a much simpler solution had been right in front of me the whole time: asynchronous actions. Instead of waiting for each action to finish being presented, I’d simply start off each action at the same time. I didn’t consider this initially because one actor’s actions can affect another; I believed that all actors’ actions had to be resolved before any of them could be presented. For example, if the player hits an enemy, and that enemy dies, the enemy shouldn’t be able to move. I still had to make some modifications to get this working, such as checking that an actor is still alive, and tracking cells that actors are moving to before they reach the cell (so that other actors don’t attempt to move into the same cell).
  • Pathfinding improvement. Over time, I’ve changed my mind on how actors interact with other actors and objects that are diagonally adjacent. I may change my mind again, but what’s certain is that there needs to be a way to allow interactions with adjacent objects in ordinal or cardinal directions, depending on the object. Currently, a melee attack can be performed from an adjacent diagonal cell, but opening a door cannot. Until this week, the latter was not possible because of a limitation in the pathfinding code – since actors can move diagonally, and the pathfinding code finds the shortest route, paths end at a cell diagonal to the cell containing the object being interacted with. The fix for this was to change the path destination based on the interaction range of the object. An object with a range of 1 can only be interacted with if the actor is adjacent to the object in a cardinal direction. 
  • Better debugging statements. It just occurred to me that I’ve written a lot of bad debugging statements. I typically add debugging statements while troubleshooting a particular issue. They make sense when working within the context of an issue, but not on their own. Without context, they do more harm than good because they increase cognitive load, which is already high from being in troubleshooting mode. I improved these statements by adding more relevant state information to them. I also rearranged the statement in some cases so that the subject of the statement (actor, item, etc.) was at the beginning of the statement. This made it easier to skim through the debug log.
  • Inspector improvements for ScriptableObjects using Odin. To reap the full benefit of Odin, I added Odin attributes to all classes inheriting from ScriptableObject. These objects are now easier to view and edit in the Unity Inspector.
  • Duplicate door bug fix. Doors recently stopped opening when they were clicked. Actually, most doors didn’t open but a few did. I reviewed the pertinent code but couldn’t find a problem. I started a game and right-clicked on a door to open the Inspect Panel, which shows everything in the cell. Nothing appeared to be out of the ordinary, and the door opened when I clicked it. Then I clicked another door. This one didn’t open. I opened the Inspect Panel and found the problem: there were two doors on the cell. It turns out that the recent change to track connections between rooms in both rooms caused most doors to be added twice. The fix was trivial; I just had to exclude door creation on the duplicate connections.

Next week, I’ll further optimize map generation. Possibly, I’ll start coding the procedural history generation, which I’ve been slowly designing over the past month.

Weekly Update – Nobember 18, 2022

The algorithm for determining available space is fully working. I really struggled with it. I expected needing a few days to complete it but those days turned into weeks. The algorithm design itself was sound; it was the calculations that drove me nuts. Ample debug statements and code comments helped get the job done. Another useful technique for troubleshooting procedural generation issues is to temporarily increase simplicity, predictability, and repeatability. To debug the algorithm, I disabled the random starting point and instead set the starting point to 10, 10 and the initial feature size to 5×5, simplifying the math. By using fixed values, I knew what the expected values were (I didn’t have to calculate these in my head every time because the starting point and feature size changed each run). Using the same fixed values for every run made it easy to tweak the algorithm until the desired result was achieved.

Here are a few examples employing the new available space algorithm. Note that these aren’t playable maps; they simply demonstrate the algorithm at work.

Map generation example 1 (3×3 rooms)
Map generation example 2 (random rooms and corridors)

Other accomplishments this week:

  • Room and corridor fine-tuning. The ratio of rooms to corridors was adjusted, along with the sizes. In both cases, I compiled data from multiple hand-drawn dungeon maps and calculated min, max, median, and mean values. The median and mean values were similar when I removed extraordinarily large rooms from the data set.
  • Smarter corridors. Corridors now only connect to other corridors and rooms from specific points, based on the orientation and direction of the corridor. This helps corridors connect to other features in a more logical fashion.
  • Level graphs working again and improved. Level graphs required a few modifications to function with the new map partitioner. While I was in the code, I made some improvements. The new partitioner treats both rooms and corridors as graph nodes (previously only rooms were nodes), so I gave room and corridor nodes different shapes and sizes to better distinguish them.

New automated tests. I rarely use test-driven development, but there were some situations this week where it made sense. The calculation-heavy utility methods for determining available space were easier to develop and test independently rather than when integrated into the full map generator.

Next week I’ll continue refining the new dungeon layout. The goal of putting out a demo by year-end is almost certainly unattainable at this point due to the unplanned procedural generation work. But, I am taking a lot of time off in December and may be able to pull it off. 

Weekly Update – November 11, 2022

I continued working on the new dungeon layout algorithm. I grossly underestimated how long it would take to finish. It’s making me miss BSP.

The grid logic eats massive amounts of time. It’s just little things like mapping local coordinates to global, making sure I’m adding and subtracting 1’s as needed when working with rectangles, remembering that the y value decreases when going north and increases when going south in the coordinate system but the order is reversed when displayed on screen. My graph paper tablet is indispensable for this type of work. I have ten full pages of sketches of rooms and corridors that I used to work out algorithms, calculations, and measurements. Debugging is time-intensive too. Tracing through the code line-by-line to check values, in conjunction with running the map generation visualizer one step at a time, has been effective.

The maps are better than last week, but still basic. The generation is much more robust than last week. Except in some rare cases, there’s no more problematic behavior like orphaned connection points and overlapping rooms. I haven’t re-enabled the processors that run after the basic level structure is created, so the map is unpopulated. There aren’t even any doors. Here are some examples of where the level generation stands:

Next week is all about fine-tuning the generator to improve the map layout. I expect this to be time-consuming because it’s a trial-and-error process.

Weekly Update – October 22, 2022

I was short on time this week due to a family health issue. 

  • Acquired gold and kill tracking. The amount of gold the player acquires and the number of enemies the player kills is now tracked. These stats are displayed when the run ends.
  • Tester player class. It’s been a few weeks since I’ve run the automated tests. When I ran them this week, every one of them failed. Yikes! Most of the tests failed because of the recent changes I made to the starting classes, specifically removing items and abilities that the classes shouldn’t have at the start of the game. The automated tests assumed these items and abilities were available. I added a new player class (Tester) that has all abilities, and made that the class used by the automated tests. For obvious reasons, the Tester won’t be available to the player. 
  • Bug fixes. Resuming automated testing exposed some issues. Fortunately, none of the issues were caused by code defects; they were caused by objects that weren’t properly configured after the Stat Modifier enhancements implemented a few weeks ago.
  • Contextual map generation brainstorming. I put a lot of thought into how to arrange map rooms and elements in a more logical fashion. I already have many pages of notes on this topic. I’ve worked through a variety of concepts including procedural history generation, rules for the types of rooms that can be placed next to each other, tying stories to structure, and story hierarchies that span the entire dungeon. A concrete implementation is slowly being pieced together. It’s been helpful to list examples of dungeon levels that the generation would ideally be able to produce, such as a level divided into two sections occupied by opposing factions.    

Next week, an initial, though likely rudimentary, contextual map generation implementation is the main goal. I can’t spend much more time on this if I want to complete the demo release by year-end. I also need to complete combat / progression balancing, which is a dependency for finalizing actor and item stats.

Weekly Update – October 14, 2022

Play-testing at the beginning of the week produced a long list of improvements and bugs to work through. There is no better method for prioritizing work than playing your game (or better yet having others play it too).

  • Finalized the Death Screen. Consisting of a text message and “Try Again” button, the death screen is basic, but will suffice for the demo release. It looks simple but took some effort to hide and disable things – stat meters, status icons, enemy health bars, hotbar, player input.
Death screen
  • Finalized the Won Game Screen. 
  • Separated actions that entities can perform and actions that can be performed on entities. A few times throughout Legend’s development I’ve unintentionally used an object for multiple purposes. This was the case with the entity ActionTypes collection. Originally this object was used to store the types of actions that can be performed on an entity, e.g. the Drink action for a Fountain, the Open action for a Door. Later, I started using it to also store the actions that an Actor could perform. I’m not sure what I was thinking at the time, but I clearly wasn’t thinking enough, especially considering how simple the solution was – two separate collections.
  • New Map Elements
    • Portal. The Portal appears on the last level. When players step into the portal, they exit the dungeon and win the game.
    • Debris Pile. This replaces the Grass Map Element, which didn’t fit into the dungeon setting. The Debris Pile blocks movement and sight and is destroyed in one hit.
  • Minor improvements
    • Turns remaining added to status effect tooltip.
    • Magic bar is hidden for player classes that don’t use magic.
    • When an actor is healed, the floating text showing the number of HP is now green instead of red.
  • Bug fixes
    • The Inspect Panel doesn’t work. Recent changes broke it.
    • Torch light doesn’t appear when the game starts. When I removed all the starting items from all player classes (many of which were added for testing purposes) and re-added them, I forgot to give each class a torch.
    • Torch light doesn’t disappear when a torch is unequipped. I discovered that the torch game object was being added to the player twice. The torch was being added twice because two different events added torches. Two different events exist because one event is triggered by equipping a torch (needed on level 1, when starting items are automatically equipped) and the other is triggered by setting the quick switch slot (needed on level 2 and after because the torch light has to be re-instantiated and the equip event will not fire because the torch is already equipped). I modified the second event to check for the existence of a torch light game object before instantiating a new one.
    • Inspecting cells with multiple entities of the same type doesn’t work.
    • Finite status effects have one extra turn. I had no idea this was happening; adding the turns remaining to status effect tooltips exposed this.
    • Flames show damage floating text when they burn out.
    • Player is able to open doors that aren’t adjacent.

Next week will look similar to this week. AI isn’t being properly saved/restored across levels or saved games. This will be a pain to fix but is necessary. There’s some interesting map generation work to do too, though I’ll need to be careful to avoid getting carried away with it.

Weekly Update – September 30, 2022

This week’s activities were all over the map.

  • New Abilities
    • Kill Touch and Charm Animals. Kill Touch instantly kills an adjacent target. I need to put some limits on this, such as resistance for stronger enemies. Charm Animals charms all animals in a 5×5 square
    • Warding Glyph. Places a glyph on the ground, visible only to the caster, that harms the first actor to walk on it.
    • Destroy Undead. Destroys all undead enemies in a 5×5 square.
    • Cure. Cures poison.
Kill Touch and Charm Animals
Warding Glyph
Destroy Undead
  • New Item: Poison Arrow. Poison Arrows inflict poison on their target. 
  • New Map Element: Poisoned Fountain. The water in this fountain is poisoned, giving it a green hue. Drinking from the fountain causes a Poisoned status effect. The fountain will have additional future uses (when the functionality is built): pouring a Cure Poison potion into the fountain purifies the fountain, an empty vial can be dipped into the fountain to create a Poison Potion, and a weapon can be dipped into the fountain to give it a temporary Poison effect. 
  • New sound effects. Cauldron bubbling, fountain trickling, egg cracking and hatching
  • Particle effect for conjuration. It’s a slightly modified version of one of the particle effects from the Pixel Arsenal particle effects package.
Summon particle effect
  • Enemies can open doors. Gone are the days in which the player could flee from enemies simply by passing through a doorway. Enemies can now open doors to continue their pursuit. Whether a particular enemy can open a door is governed by the actions it can perform. Some enemies, such as giant rats, are too unintelligent and/or physically incapable of opening doors.
  • Combat balancing. Several hours went into fine-tuning the damage formula and damage progression in a spreadsheet. My naive original damage formula was [Damage caused by the attacker] – [Damage reduced by the defender]. I liked the simplicity and intuitiveness of this calculation but it didn’t work in practice. I did a bit of research on damage formulas used in RPG’s (in hindsight, something I should have done before any spreadsheet work or coding, because designers have already figured this stuff out) and found some alternative formulas. A common formula is [Damage] = [Attack] * C / ([Defense] + C), where Attack is the damage caused by an attack before applying damage reduction, Defense is a  C is a Constant, usually 100, and Damage is the resulting damage after damage is applied.
  • Assumptions and constraints:
    • Max HP never changes.
    • Max HP only slightly varies between different classes.
    • Each class fits into one of the following armor categories: none, light, medium, heavy.
    • Each armor category has a target number of hits before dying (none:2, light:3, medium:7, heavy: 12). The damage formula and item stats are all designed around these numbers.
  • Tooltips for stats. Tooltips are now displayed when hovering over player stats.
  • New automated tests. Cell area utilities.
  • Fixed AI bugs.
    • Actors don’t track the player’s last seen location in some scenarios
    • Actors sometimes choose to respond to the wrong observation because observation weighting isn’t taking player regard (friendly or hostile) into consideration
    • Player allies sometimes follow the player instead of attacking enemies of the player
  • Fixed other bugs.
    • Inanimate objects, such as stairs, barrels, and doors, are subject to charming. It doesn’t make sense for entities without brains (and some with brains) to be charmed, so they’re now excluded. That said, this bug may inspire a future spell where inanimate objects are brought to life.
    • The Run ability allows the player to run past an enemy.
  • Changing levels and saving/loading working again. The reinstantiation of a previously visited level breaks more often than any other functionality in the game. So many changes break it. Most recently, adding theme-specific sprites for certain objects caused it to fail. 

With the last quarter of 2022 starting this weekend, I’m putting together a plan to get the first playable version done by year-end. Next week’s goals will be determined by that plan.

Weekly Update – September 23, 2022

I threw away a big chunk of the code that I just wrote last week for Stat Modifiers. I made this decision when it became evident that maintaining a running sum of all Stat Modifiers for each Stat Type wasn’t worth the complexity. I originally chose this approach for efficiency; since the modifiers didn’t change that often, it would be more efficient to precalculate and store the values. In practice, however, the values are used infrequently enough that recalculating them every time doesn’t impact performance. The added complexity, primarily due to having to synchronize the running sums with the individual modifiers, increased the implementation effort and defect risk. In general, it’s preferable to store information in one place only.

  • Improved Inventory Panel. The number of inventory slots has expanded from 24 to 32, the panel has widened, and combat stats are now displayed.
Inventory with stats
  • Tooltips for stat bars and status effects. Hovering over the Health, Stamina, or Magic bars, or a status effect icon, now displays a tooltip with additional info.
  • Actor Debugging Panel. A new toggleable panel displays detailed info about active actors, including location, vitals, status effects, and AI state. It’s been very useful for squashing AI bugs. This is a developer-only feature; it won’t be available to players.
Actor debugger
  • Bug fixes.

Next week starts with closing out AI bugs, followed by validating combat calculations (now that Stat Modifiers are in place) and continuing to add sound effects. Also, I’m going to start on interactions between items and objects, such as scooping water into an empty vial from a fountain. 

Weekly Update – August 19, 2022

This week, there were lots of miscellaneous improvements that stemmed from play testing. The summoned swarm of giant rats continued to be a source of both AI improvements and entertainment. I took great sadistic pleasure in hitting one of the rats to turn it against me and then watching its brethren surround it and rip it apart in my defense. The Conjurer class is going to be fun…

  • AI enhancements
    • Summoned actors now turn hostile if attacked by the summoner.
    • Actors will now attack the closest target rather than an arbitrary target.
  • Actor items now transfer to the actor’s corpse. Previously, when an actor died, all of the actor’s items were added to the cell that the actor died on. Now, the items are contained in the actor’s corpse and can be taken by inspecting the corpse.
  • Random actor items can now be limited to specific item types. This enables actors to carry items that are more applicable to their descriptions. For example, a bandit may carry some gold and throwing knives, while a wizard may carry some scrolls and potions.
  • Actor actions. That actions that can be performed on an object or item are defined by that object or item. Previously, any actor could perform these actions. This didn’t make sense in some cases. For example, a blue slime isn’t intelligent enough to open a door (and doesn’t have hands of course!). Actions that an actor is capable of performing can now be defined.
  • Extended Tier system to actors. Each item has a Tier attribute that indicates the item’s power relative to other items. Tier is used during map generation to ensure that the items placed on the map are appropriate for the dungeon level. I extended Tiers to actors for the same reason. Plus, for actors that have randomly generated items, the possible items can now be limited to the actor’s Tier.
  • Gold is represented as an inventory item for non-player actors. The player’s gold is tracked and displayed in a counter rather than a physical item in the player’s inventory. Now that other actors can carry gold, gold needs to appear as an item that can be taken in the actor’s inventory. When gold is dragged into the player’s inventory, it is added to the player’s gold counter and removed from inventory.
  • Actor sprite support for map themes. Tiles, such as floors and walls, have had theming (caves, dungeons, etc.) from early on. Changing stairs from tiles to actors necessitated adding theme support for actors too. This was trickier than I anticipated. The implementation for tile themes wouldn’t work for actors. The solution was to set the sprite during actor instantiation, based on the theme of the cell where the actor was located. But, that didn’t work when I tested it. It took me a while to realize what should have been an obvious problem – actors were instantiated before their locations were set, so the instantiation method didn’t know which theme to apply. I added an optional parameter to the instantiation method for the theme. It’s a bad hack, but it works and its scope is limited (there are very few actors that will have a different sprite for each theme).
  • Bug fixes
    • Tracked actors aren’t re-evaluated when an actor’s attitude toward the player changes
    • The camera doesn’t follow the player when the player was temporarily controlled by AI (such as under the effect of a Fear spell)
    • Rats can be summoned in walls
    • Rats are able to take items
    • Dragging from the hotbar causes the screen to scroll
    • Clicking the hotbar after scrolling the screen causes the player to move to the cell under the clicked hotbar slot
    • The default player click action for a dry fountain is attack
    • Mass Fear stopped working on the player

Next week, there will be more miscellaneous improvements as I work toward making Legend fully playable. I need to stop putting off the decision of how to limit spell and ability use. This decision is required to balance gameplay. Currently, spells and abilities can be used without limits, making the game far too easy.

Weekly Update – August 5, 2022

Testing the Resurrection spell
Eggs
  • Resurrection ability particle effects. I mentioned last week that implementing Resurrection was simple. However, this week I hit a snag when I tried adding particle effects. The particle effects were triggered to start at the same time that the Resurrection effect itself started. This didn’t look right because the corpse would change to a living actor just as the particle effects were starting. I added a new Action Step for particle effects so that the particle effects could run in parallel with the Effect Action Step. Then I added a delay parameter to the Effect Action Step so that it could start half a second after the particle effects started. Now the actor appears to come back to life half-way through the particle effect emission and looks much better.
  • AI fixes. I fixed some problems that were rooted in my losing track of what the ActorTracker class is supposed to do. Actors are designed to only act when they need to, which is typically when they see another actor that they’re interested in. This is done for efficiency; the game would be too slow if every actor in the level acted on every turn.  The ActorTracker class was created to allow an actor to continue acting after it can no longer see any actors that it’s interested in, enabling the actor to pursue another actor temporarily. At some point, I started using this class to permanently track actors that an actor was interested in, and built logic on top of this new use case. When the player’s actor was instantiated, every enemy in the level added the player to its ActorTracker. That caused every enemy to check if it could see the player at the start of each turn. This is an expensive check when the player is in the enemy’s sight range, because line of sight is used to confirm that the enemy can see the player. Aside from the performance issues, the shift in how ActorTracker was used broke AI. And, ironically, pursuing actors that were no longer visible, which was the original purpose of ActorTracker, didn’t even work anymore. To solve these problems, I went to my best tools for walking through complicated flows: graph paper and a pen. I diagrammed the AI steps, located the logic flaws, and fixed them in the code. I scaled ActorTracker back to its original purpose and added comments to help me remember its scope in the future.
  • Chest and corpse user experience design. One of the oldest items on my todo list is getting chests fully working. This has been held up by incomplete design. Until this week, I hadn’t determined how the player would interact with chests, and how that would translate to the user interface. I’ve been putting off making that determination until I played some other roguelikes and CRPG’s to get a sense of how other games handled this. Downloading, installing, and playing multiple games until I found a chest seemed too time-consuming, so I resorted to watching a bunch of Youtube videos to find examples. The latter was much more efficient. 

Next week, I’ll get chests fully working and fix bugs along the way.

Weekly Update – July 29, 2022

  • New items
    • Ring of Protection. Improves the wearer’s damage absorption.
    • Ring of Evasion. Improves the wearer’s chance of evasion.
  • New abilities
    • Resurrection. Resurrects a targeted corpse. I was pleasantly surprised by how easy it was to implement this. It was 95% accomplished in the Unity Editor leveraging existing capabilities.
  • Fear now works on the player. The Fear ability, implemented several weeks ago, only worked on enemies. It needs to work on the player too because an enemy may have this ability. Also, I debated whether the caster should be affected if a Fear spell’s area of effect included the caster. I decided that gameplay would be more interesting if the caster was also affected. This forces the caster to be more careful about choosing the area of effect.
  • AI can now be applied to the player. This enables status effects that temporarily take control of the player, such as Fear. This required a lot of work to implement. I originally assumed the player wouldn’t need AI, and that assumption was reflected in multiple code locations that needed to be modified. This enables some interesting potential future capabilities. For instance, an AI could be written to simulate playing the game.
  • Fixed several bugs related to torches. Torches have been the source of many time-eating bugs. The underlying logic is undoubtedly overengineered. Torches are designed to be lit when placed in a Quick Switch Slot, and extinguished when placed back in inventory. When in a Quick Switch Slot, they generate a light tied to the carrier’s position. Lighting and extinguishing torches is handled by a Convert Item effect, which destroys the original item and replaces it with the new item. This effect is triggered by moving the torch in or out of the Quick Switch Slot. I’ve spent far too much time getting this all to work. Bug fixing has been arduous because it requires tracing through a sequence of triggered events rather than walking through code line-by-line. The implementation had to be simplified, even if that meant hard-coding. I settled on changing how Convert Item worked. Instead of replacing an item, Convert Item now keeps the original item and changes the item type and some other item attributes. It’s not as clean, but it gets rid of item destruction and creation (and the logic triggered by those events). It’s also preferable from a performance standpoint, but that was a minor concern.
  • Changing levels is fully working (again). I’ve been play-testing on a single level for months and broke most of the functionality around changing levels (example: going upstairs to the previous level placed the player on the upstairs cell rather than the downstairs cell). To support the new way that actions work, I had to add Ascend and Descend actions. I discovered some functionality that had never been implemented, for example preserving Quick Switch and Hotbar slots. This was a little tricky because the Quick Switch slot state is part of the standard actor inventory (as selected items, decoupled from the UI), while the Hotbar slot state is housed outside of actor inventory as it’s only needed by the player.
  • Automated tests added for potions, scrolls, and rings.

Next week, I’ll keep fixing the known bugs and play-testing to find more bugs. This will continue until the game is in a state where I can play it for an extended length of time and not encounter a single error in the log or exception. I’m getting there…