I found myself with more time on my hands than usual (work finished a project, and there’s a gap before the next big one), so I took a long hard look at my 0.11 plans, and actively played the game a lot – and concluded that I’d wait a bit on release. One to get rid of more bugs; the game doesn’t crash on me in testing (other than a couple of obscure bugs that are now listed and scheduled), and also because I didn’t feel like 0.11 had enough meat in it to call it a release yet. So, I focused on fixing things and implementing more of the core features needed for the next major progression in gameplay.
- Implemented a template-based serialization system to make load/save a lot more predictable, and added a bunch of “canary” values that tell me if it didn’t work. It makes saves a little larger, but it also lets the code bail out and say “something is wrong here” with an assertion rather than continuing to load invalid data. Serialization has a debug mode that shows what it is doing on the console, so I can relatively easily say “oh, I did that wrong”. I wrote about the process here.
Fluid dynamics phase 1
Water is pretty critical to Dwarf Fortress-type games (other than Rimworld!), so I want to give it good support. You aren’t a DF player until you’ve lost your fortress to a simple attempt to build a secure well!
- I implemented phase 1 of fluid dynamics, water flow. Previously, water was an impassible blob on the map that served only to make things prettier. Now, each water tile is checked per-tick (actually 1/4 of the water is checked per tick to maintain performance).
- Water that isn’t on solid ground does a scan downwards, and falls to the lowest available point that has capacity to take more water (I initially had it falling one tile at a time, but testing showed that it didn’t make much difference). I implemented a test-bed that created a sheet of water at the top of the map, and it correctly rained down and stuck to the top of solid surfaces. This actually found a few bugs in which things on rooftops weren’t as solid as I thought they were, so I tweaked that until buildings didn’t leak (water coming through your solar panels sucks!).
- Water that is on solid ground checks neighboring tiles. If one has a lower altitude, it goes that way. Otherwise, it flows to the one with the least water in it (if any). Water of depth 1 (of 10) has a chance to evaporate away. Rerunning my test-bed showed water hitting the tree-tops, flowing off the sides, and pooling – then spreading out over areas and evaporating off of large flat areas and pooling in lower areas. This was pretty much exactly what I wanted. There are some TODOs in place to have the flowing water carry items/people caught in the flow, but that isn’t implemented yet.
- The structure is in place to drown/swim, but isn’t implemented yet.
- The structure is in place to have magma flow too, on a slower tick cycle – but that is waiting for temperature effects. What’s the point of magma if you can’t catch fire/melt?
- Phase 2 will handle back-pressure, apply a few more optimizations to flow, and try to attain the goal of a system deep enough to handle U-bends. Right now, if you fill a cistern at the bottom, even with a huge drop, the cistern won’t fill above the entry-point. That’s a good start, but not there yet!
- What good is water without a way to encounter some? Maps can now gain pooled water, based on the biome’s rainfall rate (so arid regions have very few, wet rainforests have lots). They re-fill at the bottom (this will happen when it is raining, but weather is a future item).
- I still wanted more water, and I’ve had rivers on the world-gen list for a long time, so I implemented them. At the world-gen level, river starts (springs) are picked randomly from tiles that have either hill or mountain types. Each river then flows to the adjacent tile with the lowest altitude, with the caveat that they can’t visit a tile twice – so you can get big, meandering rivers. If a second river enters the same tile, it will always choose the same exit – giving nice forking rivers. Lots of adjacent water features can be morphed into lakes (but they still need some work).
- Region-gen (the code that makes the actual map when you enter an area) gained support for rivers. It determines how many rivers enter the area, and from which directions. A “confluence point” is randomly selected, and all the rivers flow to that point. I found myself not wanting to write a Bezier Curve algorithm just yet, so for now they follow a straight line – but that will change. A wider (currently hard-coded, but will be a function of how far along the river the tile is) river then leaves the confluence point to the appropriate exit. There’s a bunch of altitude selection code here, ensuring that rivers are deeper than the surrounding terrain (otherwise you can get some pretty epic – but ultimately unplayable – floods). Rivers spawn additional water at their entry point if the water has gone away (due to flow, which occurs automatically with the rest of the code, or because you decided to dig a water feature).
- This prompted some work on building placement to ensure that you could build a set of floors over a river canyon, a nice stop-gap until bridges are in place.
- A second series of tests featured mining out areas and then drilling into the river to flood them. This prompted some fixes for channel (dig a hole 1 tile down and an open space) mining, and also prompted me to make miners look for additional mining targets when they are done rather than downing tools and wandering off – it’s much easier to actually finish a digging project now.
Gravity phase 1
The other area that has been sorely lacking is gravity – falling down things, collapsing structures, etc. This opens up all sorts of potential traps, mishaps and !FUN!. So, I set out to implement it in a very general way that would automatically apply to the game in general.
- The first step was to add a system that scans everything with a
position_tcomponent and checks if they are on solid ground. If they aren’t, a
falling_tcomponent is added to them. Each tick, everything with a falling component moves down one Z-level and checks to see if it met the ground. I’m giving you a 1-Z level fall for free (no damage), otherwise you suffer 1d6 per tile fallen. Hooks are in place to apply damage to whatever you landed on, but that isn’t implemented yet.
- As a test for that system, I set an unsuspecting settler on guard duty and channeled out a 10 z-level drop underneath him. Once the shaft was in place, a miner snuck up and removed the top-level. As I’d hoped, the settler fell to the bottom and died from the impact. On a second test – he teleported ot bed! It turns out that the settler AI’s “oh no, there’s nowhere to go from here – emergency teleport” feature kicked in, because there really wasn’t anything the settler could do about it. So now, the AI checks if they are falling and screams if they are on the way down (this only kicks in if you happen to get an initiative phase on the way to certain death).
- Found an amusing bug that made not-yet-constructed buildings plummet. For example, a not-yet-constructed bridge would fall to the level below. The brave little settlers would then try to reach the fall destination and build it anyway. So I prevented incomplete structures from falling.
- Created a routine to determine “is this connected to the ground?”. The first version used recursion, was an awesomely simple thing – and crashed the stack every single time. Oops. The second version uses an in-place queue, is fast, a little messier, but gets the job done. It is checked every time a tile/building is removed – so if what you removed was holding up a structure, that structure is flagged as “not connected”, particles are emitted, and it crashes to the ground.
- Had a lot of fun with structures’ roofing collapsing due to my not having joined everything up correctly. Re-did some structures to be architecturally sound!
- Tests for this have mostly involved building things and demolishing parts to see how it crunches. It’s fun. 🙂
This is integral to some of the upcoming features: building destroying damage, and mechanisms that can trigger collapses (for example, the old support that collapses when you pull a level trick), so I really want to get this right. More testing is called for!