Item-spawning dungeon features

Testing bookshelves, aka “The Library Dungeon”

Some backend work this week, to support item generator effects in level objects. An example is a bookshelf. Bookshelves are interactive objects that may or may not contain a scroll or a tome. Initially will appear full of books, but will become empty when searched. If an item is found, it’s automatically transferred to the inventory.

An additional fine point for bookshelves is that they are generated as a single feature in the native plugin that does the dungeon generation and placement work, whereas the C# side generates a cluster of bookshelves centered around the calculated feature position. So, some extra work to make things passable, etc. This is useful as we can do several nifty things if the native plugin generator works in terms of features, and the C# side can instantiate each feature into multiple similar entities. Another example would be a boss enemy with a number of minions, but that will come later on.

Another requirement for this work is to specify some sort of 2-dimensional enum for item types and subtypes, so we can tell the item generator system that we want a “tome” or a “scroll” object that has less-equal item level than some specified.

Another random fix was the AI of ranged attackers, where they would not take into account that obstacles might be in the way of the fired arrow. Well, now they do, and move into a position that provides a clearer shot.

I’ve also started having a look at the prefab support in the dungeon generation, written last summer, as I have to write some tool to generate prefabs to use with generators.

Diagonal movement & thrown bombs

Throwing a delayed-blast bomb, and moving it with Telekinesis before it explodes

More miscellaneous work this week, under the general guise of “emergent gameplay”

  • Adjusted how diagonal movement blocking works, for QoL reasons. Before, if you wanted to move diagonally, both tangent direction had to be unblocked. So that, when moving, you never see the player going over any obstacle. I really, really disliked how that worked in practice, as 8-directional movement stops being fluid. Now, diagonal movement is only blocked when both tangent directions are blocked. This works far, far better and the movement is now very smooth again, without surprise-blocks that don’t seem to make sense.
  • Added support for thrown bombs (well, general category of thrown things that have a particular effect on landing). So that there are more AoE options for non-spellcasters, for example some elemental AoE for fighters, or some smoke clouds for stealth users, etc. I also added support for delayed-blast effects, so that you can have situations where you throw a delayed blast bomb, then it gets moved somehow (e.g. via trap, kick or a kinetic spell), and then it explodes. Russian roulette gameplay could then be to start throwing delayed blast bombs on teleport traps πŸ˜€
  • Realised the importance of a GUI for split and merge stacks, wrote some basic placeholder GUI for it, but also realised that I could not fit that somewhere reasonable based on the existing item GUI that I have… To elaborate: the current item GUI logic, which I was proud of, is to always have a pair of containers (player & ground, or player & chest, or player & other container), and you multi-select things in one container, multi-select things in the other container, and do a transfer. To help things, I also had some toggles for all/none selection per container. That’s all great, until we need to split a stack, as that just can’t happen with the multi-select which is binary in nature. So, back to drawing board for how the GUI will work for inventory transfers.
  • Added some initial support for destructible objects and durability. Again, this feature creep is essential, as breaking things is an integral part of roguelike life, as the remains of broken barrels, jars and doors from various games would all attest to. This can creep further into weapon durability, but I’m not going down that route, yet, although the deceptive simplicity of integrating that is tempting.

Creature aura pre-rendering

In-game pre-rendered auras
Aura generator tool: just save shader results to a series of images

The major thing this week was making better Diablo 2-style auras but not rendering them dynamically; instead, I made a simple editor tool (based on this) that bakes auras as texture animation, bundling everything up as a texture atlas and using that. Made the in-game aura rendering code far far easier, than having separate shaders for each. Since each texture atlas supports up to 2048 animation slices (due to texture array limitations), for 64 frames per animation I can store 32 different auras, which I think is more than enough.

Another feature that I’m in the process of adding (added, bugged, debugging now) is freezing levels when you leave. First of all, freezing is important, otherwise if all the entities from all levels play, well that just doesn’t scale. So, to avoid a boring “freeze immediately” after you leave a level, at the moment I allow for 10 standard actions (a minute in “game time”), which allows entities to suffer repeated effects, move a bit, etc.

Creature traits, auras and elites/bosses

Visuals for elites, bosses and early-draft aura look

Some refactoring and some basic feature support this week, all towards bosses and elite enemies.

  • On the refactoring side, I ditched even more ScriptableObjects in favour of my own JSON-powered flyweight classes. The changed classes were some name generators and associated data.
  • On the same topic, I’ve made a “boss name generator” for boss creatures, which creates names such as “Satha the sadistic” and “Iveri the power-hungry”. Basically, the first name will eventually be derived using a language that’s prevailing in the region where the boss will be found, followed by some villainous adjective.
  • Fixed a visual bug where some visual effects like burning, freezing etc would be seen before the projectiles that caused them landed
  • Added support for creature traits and made a few. Traits are a collection of effects applied to a creature, like providing resistances, increased base health, etc. What this plans to be is what Heroes of Might & Magic did: every creature was differentiated from others based on its stats and, most importantly, traits, which gave unique flavour to units. The difficult part is to come up with interesting traits of course, befitting the genre and gameplay.
  • Added support for auras as an effect (applicable in traits). An aura is an effect that is centered on an entity and gets applied repeatedly to all entities (either friendly, or not, or anyone) within a particular radius. For example a boss with a damage aura could cause its enemies to take damage repeatedly while in radius, where as a boss with a damage-enhancing aura would cause all its allies/minions to get damage-enhancing bonuses while in radius.
  • Elite creatures get a level bonus plus a corresponding trait for elite creatures (e.g. max health, better damage, faster, etc), whereas boss creatures get a bigger level bonus plus a corresponding trait (or two?) for boss creatures (some aura, etc). I want to have something like Diablo 2’s unique monster bonuses
  • Elite creatures have a different visual indicator to boss creatures: same effect, different colour.
  • Early draft version (aka terrible looking) for visuals to represent auras. They are rendered correctly as a ground layer, alongside decals, but they need to be spiced up. I’ll be looking of course at Diablo 2 for reference, but the rest of the work for this might be art-related or making lots of pretty shaders, which is best reserved for an “I-don’t-want-to-code-architecture” day
  • Spawned creatures can be normal, elite or boss.

Here are the Diablo 2 auras to get inspired for any further visual work on this (source 1, source 2):

Diablo 2 auras, in-game

Refactoring Effects and Status

Freezing and burning effects

Quite a bit of refactoring this week, and some bug fixing as well. Net results: level-scalable effects and more/better status effects.

Effects now are level-scalable, and they also include a source entity. Basically, I have this Effect class everywhere in the gameplay code, which previously was just a generic Effect.Execute( Entity target). While super-general, it’s also super-problematic when I want to have more info. Long story short, I realised that changing that to Effect.Execute( Entity target, Entity source, float effectLevel) is absolutely great and useful for anywhere effects are used in the codebase. So, I can have a damage effect that scales with level, e.g. 5-30 damage at level 3, and add 1-6 to the min/max for each level thereafter. Example: instead of having a lot of healing potion variations, I could even get away with just a single healing potion with level scaling. There can be still weak/strong potion variations by changing the constant/scaling values (if a weak potion cures 1d8+2d4level, a strong potion could cure 2d8+4d4level). Level scaling is optional, so many effects are of course not scaled like that. By knowing who the source is, it’s easy to track the source entity to award XP on death, among other uses. This can/should chain, for example if a player creates a trap that applies a burning effect to a creature, that dies after 3 turns of being burnt, the user should get the XP.

Status effects got a big update, to support more complex things. I’ve added burning and freezing to test a few things:

  • Can now attach visual effects to the entity with that status, e.g. a freezing entity will appear blueish, whereas a burning entity will be surrounding in flames.
  • Can now attach effects, permanent or temporary, to status effects. For example burning has a permanent effect where, for a few turns, we repeatedly apply a permanent hit points modification (fire damage), so basically damage over time. Freezing has a temporary effect where it reduces the total speed by 50% for its duration. Status effects can optionally stack, so if I decide later to have a status effect, such as poison, that can stack, then getting poisoned twice might be twice as bad. Status effects can be associated to verbal, somatic or visual components, where as active abilities might require any combination of verbal (e.g. spell/warcry), somatic (anything that requires body movement) or visual (anything that requires a clear view of a target) component.
  • I’m not going to go the route of status effects cancelling each other and changing to other things (e.g. burning + frozen cancelling each other out, etc). If necessary, it can be explained anyway as your feet are burning while your arms are encased in ice πŸ™‚

Secret things are uncovered only when they are in view, and only uncover them by the player.

I also fixed a bug with passing StructuredBuffers to Unity shaders. Unity did not want to send a compute buffer to gpu correctly when in the shader I use StructuredBuffer and in Unity I specify the buffer with a size of 4,6,8 uints and a few more. Yes, totally reasonable, but it should warn me maybe that shader and ComputeBuffer do not match?? Ugh, spent 2-3 hours on that at least.

Entity effects & more

Entity effects: boss aura and an early prototype of a burning effect

Several “random” bits of work again this week, as my TODO list is a hydra. Solve one entry, and poof more appear. Not as bugs, but more like a dependency tree where you try to resolve a node and the fog of war dissipates and reveals more things that are not in place πŸ˜€

  • Framework for rendering “entity effects”, graphical effects attached to (and dependent on) a particular entity. Two examples are shown in the video above: a boss aura like Dungeonmans, and an early version of a burning effect.
  • Some debugging GUI for increasing attributes/skills
  • Added potions and tomes that increase attributes and skills
  • Allow per-creature blood spatters (on injury) and blood pools (on death). So that skeletons, ghosts and oozes don’t bleed.
  • Refactoring my configuration database to not have dependencies at all, as it got cyclic. So, I have for example the configuration for a creature race that depends on the configuration for an effect. The moment I started adding more effects, one of the effect types might depend on a creature configuration, so the dependency chain got cyclic, so I had to get rid of that. Now I’m doing a two-pass approach: first I load all the configurations in their most basic form and register their name (“allocate” them so to speak). Then in the second pass, I read everything normally, and order does not matter as I’ve allocated all the configurations by name in advance.
  • Machinery/planning for how a creature gets to learn active abilities (spells, active skills, special attack moves, etc):
    • Monsters have a mapping of ability-to-level-requirement. E.g. a monster wizard of level 3 would know firebolt, same creature level 7 would know fireball.
    • Equipping particular weapon categories might allow for special moves. E.g. equipping polearms would give a skill (that costs energy) which attacks from 2 tiles away
    • Progression in mastery levels in particular skills can unlock some active abilities.
    • Consumable items (e.g. book)
    • (tbd) Special dungeon objects
    • (tbd) Shops/trainers/people in cities
  • Try to be a bit more gamey and less simulationist with items and effects. Equipment will not have on-use effects, whereas potions should generally have different effects than, say, scrolls or items that can be used (wands? etc). Potions and scrolls should be different, beyond “this can get wet” for example

Refactoring abilities & input

Player getting blasted by AI’s ice AoE spell, then obliterating them with a multi-fireball. Yes, terrible and over the top, but it’s useful for testing πŸ™‚

Mainly refactoring work this week, with the purpose of unifying the handling of abilities by the player or the AI. Concrete outcomes of this refactoring process:

  • New WIP ability, usable by both player and AI: fireball.
    • We need to select a target position
    • It has an area of effect
    • It’s effect is “fire damage”
    • It spawns a particle system, initialised with the positions of affected tiles
    • Fireball itself is a projectile, so if it hits an obstacle, it will stop (and explode)
    • We can setup the skill to be able to shoot multiple ones, homing ones, or both
  • AI can use the fireball skill too, by evaluating the “score” of each possible tile, determined by the possibility to hit ourselves (-5 score), allies (-2 score each) and enemies (1 score each). A score greater than 1 means “good enough”
  • AI applies a recency-based weight when choosing a skill to execute. So, a no-cooldown skill that was just executed will have 75% weighting which goes back to 100% after several standard actions. This is to avoid repetition of maximally scoring abilities, and make the AI hopefully feel less robotic.
  • New WIP ability: magic missile.
    • Like fireball, but does damage to a single tile
    • Number of projectiles can be greater than one, and for each projectile we can select a different target. Homing missiles still need a clear path to a destination
  • Reworked item transfer interface a bit, so that when transferring items between player/container or player/ground, we can select a number of items from container, a number of items from the player, and then click a button to make the transaction. So that with a standard action we can move e.g. 2 items to the container and pick 3 items up.
  • Cleared up input/keys a bit (still far, far from final though). Now we have the following categories of actions:
    • Ability-based: Fireball, melee attack, ranged attack, use item, etc. These typically need to be configured with some gui (which item? which direction? etc). Most of these would not appear in an input configuration file, as we have just too many. Instead, they’ll be mapped to…
    • Hotkeys: so that I can map any ability-based actions to hotkeys. Whereas the essential actions are limited (move in a direction, primary action, etc), most abilities have to be mapped via hotkeys.
    • Actions that do not require a gui, e.g. quicksave, quickload, teleport, look.
    • Press-and-hold actions, e.g. highlighting of creatures or objects, minimap view

Effects & Enchantments

A typical RPG/roguelike has equipment, cards, skills etc that can all provide bonuses or penalties in various statistics, primary or derived. For example, movement speed, attack speed, maximum health, etc. There are possibly lots of ways to implement them. After a few unsuccessful theoretically-efficient approaches, the current revision looks reasonable.

Temporary, permanent, recurring and conditional effects

Examples that should be possible include:

  • Potion of healing: one-off, adjust current health [permanent]
  • Potion of regeneration: every N seconds, adjust current health. Stop after N*K seconds [permanent, recurring]
  • Potion of speed: movement speed increased by 20%, for 1 minute [temporary]
  • Potion of remove blindness: one-off, sets blindness to false ONLY if the creature is not naturally blind [permanent, conditional]
  • Sword of orc slaying: +50% weapon damage (applied when we use an ability that uses the equipped weapon), only if we’re attacking orcs [temporary, conditional]
  • Boots of the desert: +50% movement speed when crossing the desert biome [conditional]

So, observing such use-cases, I made the following design choices:

  • All types of effects/modifiers can have conditions
  • Permanent effects write directly to the actual values the affect. A health potion updates the actual health value. If they have conditions, they check them once, before they apply the permanent effect.
  • Temporary effects are stored in a separate list. Every time we want to get a particular value (weapon damage, max health, etc), we need to run a system function that gets the base value and applies all effects (if any of them has conditions, it’s applied if the condition is satisfied)
  • Events are set up for recurring permanent effects
  • Events are set up for temporary effects, to remove them from the list when the effect expires.
  • Temporary effects can be applied when an item is being carried (figurine), equipped (sword) or used (potion).

A drawback of the separate list for temporary effects is that we have to maintain indirect access to all variables that could be modified by effects, so that the access function always takes into account any effects. Additionally, we can’t even cache effects as they can be conditional. So for example, we have functions like “EvaluateMaxHealth”, “EvaluateMovementSpeed” etc, that get the base maximum health, then look for any effects that target max health (and pass the conditions, if any) and generate the final value.

In the temporary effects list we have 3 types of effects, based on what they modify:

  • Numerical. +1 Skill, +50 health, +15% attack speed.
  • Boolean. Set paralyzed or not, deaf or not, able to fly or not, etc.
  • Option. Set field of vision algorithm. Probably more to come.

Overall the system should be very flexible and allow for weird effects expressed naturally by data driving, e.g. a potion of lycanthropy that only works in a full moon, over the dead body of a wolf (a date condition and a condition that there’s an item pile that contains a wolf corpse under the feet of the player). The only important rule to remember when developing effects is that any values that can be affected by temporary effects need to accessed by these evaluator functions, unless we do not want to take modifiers into account.

Here is a video that shows a few potions in action: healing, emergency healing (only when health is < 20%), alacrity (player moves at incredible speed), X-ray vision (see through walls), oracle (see entire map):

Here’s another video that shows the death particle system adjusted to go towards the direction of the hit:

Skill System Redux

Previously…

Last time I worked on the skill/stat system, I didn’t even touch active abilities, like feats in DnD. My main problems with my skill breakdown were:

  • Too many skills: around 50. Easier to navigate by using categories, but still.
  • Skill progression is difficult because of the skill breadth. Difficult to balance a jack-of-all-trades and a focused grandmaster of a few skills, with interesting skill progression/bonuses
  • The options were many, and the bad options could be many too. So, level ups would be a bit confusing and prone to mistakes and bad builds
  • Only some of these skills would enable DnD style feats, but I hadn’t thought that part out, and it would possibly be imbalanced.

So I engaged in some thinking, and some more thinking, and tried to recall bits of advice and suggestions by a variety of game/RPG design people, most importantly for what I want the core experience to be like. And the cornerstone pillars of the game’s experience is combat and exploration. But at the same time, I don’t want to ignore stealth or NPC interactions in cities/factions/elsewhere, so these exist but are of lesser importance, and this should be reflected in the system.

New approach

So, while the skills needed revamping, I like the attributes and the mastery levels. So, here are the main components of the current train of thought:

  • There are about 20 skills in 4 categories: offense, defense, arcana and misc.
  • Players can put points in each skill up to a limit of 15 skill points.
  • Players can improve their mastery of a skill given sufficient points and training from an NPC
  • Improving the mastery of a skill gives new passive bonuses (e.g. evasion chance when adding points in light armour skill). Points in a skill improves those bonuses.
  • Active abilities (think DnD feats, or ToME active skills) can be learnt from NPCs or scrolls, if player satisfies requirements in terms of skills, attributes and masteries. For example, crafting light armour would require mastery in both crafting and light armour.
  • Each level, the player can allocate 3 skill points, to a total of 90 skill points at level 30. Only one skill can be trained to grandmaster level, and thus reach the 15th point

Here’s the current list of skills:

Offense
Dual wielding
One handed
Two handed
Ranged and thrown
Defense
Light armour
Medium armour
Heavy armour
Shield
Arcana
Command magic
Alteration magic
Divination magic
Creation magic
Destruction magic
Misc
Athletics
Meditation
Lore
Stealth
Crafting and alchemy
Survival
Leadership

The idea behind this is that skills reflect play style. My goal is to make as many as possible viable play styles, mixing arcane with melee, etc.

Archetypes

Previously I had to manually author archetypes, as not all combinations of skill points would be valid. With the new approach, it should be easier to write an automatic generator of characters that does not use any predefined limits in masteries etc. If a barbarian wants to learn meditation, more power to them, it’s going to be useful still. What is required to be careful about is the related attribute. If your barbarian has very low intelligence, it’s an indicator that he/she won’t really master that skill. The goals of the archetype creation are:

  • Maximum diversity
  • Minimum bad-looking builds
  • Fully procedural builds (“make me a character”)
  • User-guided builds (“make me a grandmaster in destruction magic, with some other nice skills too”)
  • Minimum data entry / configuration effort

The new archetype generator is parameterized on a list of target skill/mastery combos to achieve (optional), and a “well-roundedness” factor, which represents how hyper-focussed or jack-of-all trades we want the character to be. It works roughly in the following way:

  • Character creation: If we have target mastery combos, roll and initialize stats to satisfy requirements as close as possible
  • Assigning attributes: Try to satisfy criteria. If done, allocate based on the well-roundedness, between completely randomly (well-rounded) and to the highest attribute (focused)
  • Assigning skills: Try to satisfy criteria. If done, allocate based on attributes and well-known combos, e.g.:
    • dual-wield + one-handed = good
    • two-handed + dual-wield / shield = bad
    • meditation + any magic = good
    • sneak + heavy armour = bad

And that’s it! Yes it’s oversimplified a bit, but the archetype generation code is less than 300 lines, and is much, much, much simpler than the old approach. So, what characters does it generate? Plotting time again!

Well-roundedness = 0, target Destruction Magic GM: What looks like a typical elemental mage, plus misc skills as he’s physically weak
Well-roundedness = 1, target Destruction Magic GM: A more well-rounded machine of destruction
Well-roundedness = 0.5, target Destruction Magic GM: Something like the above, but in-between
Well-roundedness = 0, targets Dual Wielding GM and Light Armour Expert. Adding relevant athletics and dual-wielding; we are quite agile after all. At the later levels we develop Leadership as well, since we’re high on the Charisma.

So, archetypes look like they are working as intended. For next time, instead of fast encounter resolution, like last time, I’m going to deal with HP/MP next and attempt something more concrete, like spawning a few aggressive creatures with levels, and progressing with connecting skills to active abilities.

A little bit about locking

Let locking be the means to prevent use of some sort of device

Things that can be locked:

  • Door (use = open)
  • Chest (use = open)
  • Magic portal (use = enter)
  • Fountain (use = drink)

Types of unlock conditions

  • Have XYZ key in inventory [active] [E]
  • Actor being of a specific race, or having XYZ traits [E]
  • Have all fragments of the key in inventory [active] [E]
  • Time being midnight [W]
  • A pressure plate being pressed [W]
  • A lever having being pulled [W]
  • A creature being dead [W]
  • Any combination of the above, etc

Entity and world conditions

The unlock conditions are grouped as [E]ntity conditions, where the entity that tries to unlock should evaluate those to true, and [W]orld conditions, where when the state of the world changes with respect to the given conditions, the lock might activate/deactivate.

An actor can’t explicitly lock or unlock a door with world conditions, as they need to change the state of the world in order to get these doors locked/unlocked (e.g. leave an item on a pressure plate, etc)

Active and passive entity conditions

[Active] entity conditions are ones that sort of require the user to do something, if we play out the scenario. Obviously no door opens because a key is in our pocket, but if it is in our pocket and we handle the door, and the door is locked, we can imagine us getting active and using that key in the lock.

Passive entity conditions don’t need the actor to do something explicit. The actor handles the door, but the check happens in the background. The main difference gameplay-wise is that if there’s a lock with a passive condition, after unlocking, the actor can’t lock it again, as it is beyond the actor’s actions.

Back-door

Locks can specify an unlock direction, so that if we somehow ended up in a locked room without the key (e.g. stepped on a teleporter trap), we can open it from the inside. Doing this bypasses all conditions, entity or world conditions. If we do have world conditions, the next time any of the condition changes (to any value), the lock will get enabled/disabled appropriately

Here’s a video that shows a scenario of a lock with world-state conditions only: 3 pressure plates that need to be pressed, but we can also unlock from inside