I've been naughty and I've been refactoring again, but it was important. The pattern is the same: I'm working in some area (e.g. traps, skills, effects, etc) and I notice some problematic behaviour, and sometimes that problem is ... systemic. So, I get my shovel and start digging. That's how the last several weeks have been! But for a good reason, as upon emergence, it's not like the code is cleaner, but more things are supported, in a better way. Lots of generic talk and little action, so here we go.

Leap skill (video)

Just a bit of content, something that dawned on me. In a typical roguelike you have narrow corridors and the occasional trap there, which makes things tricky as you can't avoid it. How to deal with it? Many ways, apparently, most implemented. Disarm trap, teleport over trap, throw a hook and pull yourself over trap, throw item on trap to trigger it. And now, you can leap over the trap. This is a skill suitable for the more martial and agile characters, so that they have ways of dealing with such situations too. Here's a video

Core values and disposition

Cities specify a percentage for each core value - these could be things like prestige, prosperity, technology, might etc. Cities and sentient races also specify a percentage for each type of disposition, that are a number of axes slightly more granular than the law/chaos good/evil of DnD. Based on these, a city will contain a composition of races compatible to its disposition. All this is for overworld-level work, which is not player facing right now during dungeoneering, but it's going to matter later. I went hunting for some icons for core values and dispositions, they'll be seen at some point in the near future. This was one of my treats for doing too much refactoring :)

Leap over trap

Traps

I revisited the trap code, and it was ... bad, due to some bad/inconvenient abstractions. Long story short, did a bit of refactoring, and added a couple more traps. Traps can fire any sort of spell/ability, so one can get really creative. For my examples, I was not really creative, so I just added a trap that shoots a fireball, one that shoots a magic missile, another that pushes the player into a random direction, another that pushes player towards the direction of movement (or against), some trap that fires homing throwing axes (because, why not?), you get the point. Extra fun is chaining traps, so you can fall on a "push" trap, which pushes you to the "push to random direction" trap, which pushes you to a fireball trap's pressure plate. Maybe I should add a pinball easter egg level in the game, it's totally possible. So, the chained traps uncovered some bug in timing, so ... bug hunting time!

Trap chain

Debugging graphics

I almost fell into a rabbit hole for this one. When implementing traps, I noticed some errors regarding animation/timing. The rendering code has become more and more complicated, and testing is not great (I mean, how do you even test a complex turn-based rendering pipeline? I didn't stay long enough in professional gamedev to let that sink in...). The problem was exacerbated by the fact that when I enabled more granular logging, I ... understood nothing about the error, so I despaired. Does my pipeline need rewrite? I mean, it would benefit. Do I have the energy/motivation to do that? Hell no! What do I do? Well, the bloody obvious... Improve logging! And so I did, and lo and behold, after 1h of extra logging code and 1h of debugging, I solved not one but two mystery graphics bugs. Changes need to be tested more thoroughly, but that was bloody promising. Which means, I can move on to different things, which are not refactoring! Yay.

Music

I felt like dabbling with music last Saturday, so I dusted off the guitar and MIDI keyboard, fired Reaper and started messing around. I usually fool around with the guitar because it's far harder to record something that is good enough and suitable for video game music (at least for my game). But I did end up recording something on the MIDI keyboard, a few layers of a melancholic simple piano lines. Have a listen here!

GUI Item Description Panel

After the above side-work, back to this panel, more iteration. One of the most visible changes is the font, previous one was nice and cool but not suitable for numbers. While I believe this is an improvement, I still would like a good small caps, but I haven't found a suitable one. After about 15-20 mini tasks related to that (bugs, prettifying, etc), I think ... it's ok for now! I'm not sick of it, it's just diminishing returns. Time to move on to (or, better, resume) the next topic... almost! I need to carry on with iteams/enchantments, just for a little bit more.

Carry, equipped, native, non-native enchantments: I'm not charmed

Last bit of craziness I'm trying to deal with, but it just doesn't click yet. So, let's rubber duck a bit:

  • Enchantments in equipment can be "native" or "non-native". Native enchantments are ones that make sense, e.g. an enchantment on boots can provide movement speed, whereas an enchantment in a sword can improve damage. Counter examples are a shield that improves damage and a sword that improves evasion. This property is pre-defined for each enchantment type.
  • Enchantments can apply to the item or the owner (or both). Item enchantments can be e.g. improving attack speed for a weapon, or the defence rating of a shield. Owner enchantments would be movement speed, attribute/skill boosts, but also attack speed, defence rating and others. This property is pre-defined for each enchantment type.
  • Enchantments can activate when the player equips some equipment item, but some other enchantments can be always active if you have the item in the inventory ("on carry" - think of charms in diablo 2). It's a "dangerous" thing to support such charms, but I have a few things in mind where they make sense.

Given a set of enchantment groups (e.g. attribute boost, skill boost, movement speed boost, etc) I'm trying to figure out a way to support the following scenarios:

  • Regular equipment enchantments, requiring native enchantments. The bonus should apply to the item by default if possible, otherwise to the owner
  • Rare equipment enchantments, preferring native enchantments but allowing others too. Otherwise as above
  • "On carry" enchantments for charms. All enchantments that are applicable to the owner should be possible here

The reason for the above, is because I've been trying to add nice support for "charms". Charms are items that provide bonuses (and/or penalties) passively by just having the item at your inventory. This is an idea from Diablo of course, but that's where the similarity ends. Due to potentially large inventories in roguelikes/RPGs, you'd just end up hoarding/grinding for charms, which is not a great idea, as they would eventually diminish the value of swapping equipment. After a bit of brainstorming, I came up with some legit and interesting modifications and use-cases, and the one that's implemented right now is simply this: every charm is generated with two enchantments: a positive and a negative one. This means that you don't want to just hoard them in your inventory, but be quite selective about them really. Eventually, you would possibly be able to hoard them in some static chest and occasionally revisit and pick ones for the occasion (e.g. fire resistance bonus charm if you go to a volcanic area), but of course that has time/effort cost, so it's not a lucrative approach to apply all the time.

Text contrast


My little fugly WCAG helper

Originally, I wanted some orange/brown-ish background colour for menus and GUI. But I also wanted white font. Apparently, these don't go well together. So, over the course of several months I darkened the background enough, so that it passes some accessibility tests. All good, right? No. As in typical RPG GUI fashion, I want to have color in fonts, for different circumstances. E.g. in the item inventory panel, when showing bonuses/penalties of items, I can colorise with green/red. Green is fine, but bright red (255,0,0) is definitely not fine, with my brown background. Now here's the fun bit. How do I find a red color that passes the contrast check? I looked around for any tools to do that, I couldn't find any. Some kind folks shared their approach, which is manual, testing colours individually. So I thought I'd try some sort of automation. I had a look at what formula is being used to evaluate text contrast, implemented it in python and made it fast enough (~2-3sec) to generate contrast of a colour to all possible colours in the colour cube, and more importantly, highlight the colours that pass the required contrast test. I made a GUI version using tkinter, which looks fugly but does the job as well, and I can use a slider to see the different RGB slices (slider controls the red component). Long story short, this tool tells me that there's no chance I'm going to get a good contrast with a red font colour, not even with a purple one. What does this mean? Other tricks are necessary. I got a few opinions, e.g. adding a dark outline or a dark background panel to the text rather than the entire UI, but I fear the latter is much work to make it look nice. So verdict currently is to eventually revisit the game UI's backgrounds and make them an even darker shade of brown. But this time I'll be prepared - I'll use the tool in advance to generate font colours and a background colour that all work well together. More to come on this front

String to StringName

I read a nice proposal comment in my godot digest, about GC spikes because of use of strings in functions like Input.IsActionPressed(), because if you provide a string, it converts it to a StringName each time apparently, doing heap allocation. I went and revisited my code, and I saw that I call that function a lot of times each frame. Hmm! So I wrote a python script to 1) visit the entire codebase and figure out what pure strings I'm passing in that function 2) create static class that contains the equivalent stringnames, statically constructed and readonly 3) revisit all the original occurences and replace the pure strings with StringName. Done!

Upgrade to Godot 4.4

This went mostly uneventfully. I get lots of weird errors in 4.3 that don't seem to have a direct effect on the game, and I only get more with 4.4, but things work, so I'm cautious.

That's it for now!