Dwarf Fortress Tarn Adams talks about game development
For a long time, one of the best ways to use powerful processors for entertainment was Dwarf Fortress - a game in which the whole world consists of ASCII characters, and which will gladly eat a gigabyte of memory and a large fraction of the processor time.
But unlike some other games, in the case of DF, the player feels that she really needs everything that she requires. Her detailed calculations create a whole world with buildings, cities, merchants, rivers, volcanoes, monsters and, of course, gnomes. If one person created all this, this would be a terrific achievement; Dwarf Fortress is a program that creates all these objects on its own.
The author of the game, Tarn Adams, agreed to answer our questions about his creation, which, despite the existence of many imitations, still remains a completely unique game.
Gamasutra: we heard that Dwarf Fortress is coming out on Steam and itch.io in a paid version with a simplified interface. They say that this is due to your upcoming treatment costs. Is it hard for an indie developer to stay afloat?
Adams:Yes, a lot of effort has to be made in order not to leave the business, and almost all developers are forced to invent creative ways to be able to pay rent and other primary needs. And sometimes you run into problems that you cannot handle. We were lucky so far, but we have to change over time: quit and get a new job, switch to Patreon, and now on Steam with itch, or draw pencil drawings, which is very far from creating video games.
What does your day-to-day code building work for Dwarf Fortress look like ? What languages do you use? What libraries? What IDEs if you use them? What computer are you developing the game on?
I work on a regular, unremarkable Toshiba laptop with Windows 10. I write code on a terrible C / C ++ mix in MSVC Community. For the legacy version I use OpenGL, for the main version I use SDL, and for the sound I use FMod. I do not use anything else on Windows, with the exception of part of the headers from MSVC (and before), which I have been using for decades. I am not completely aware of what is happening in the Linux / Mac versions because I do not develop them on a regular basis.
Dwarf Fortress has been under development for almost 17 years, and its code base must have reached gigantic proportions. On my machine, creating a world with standard settings requires more than 1.2 gigabytes of RAM. The problem with such megaprojects is that they become too large and do not fit entirely in the brain. What strategies do you use to keep the project intelligible and understandable for work?
I have a strict naming system, and I don’t save on long names of variables and functions, so that everything is readable even after a few years. In general, I try to take care of the future myself. All my comments in the code are aimed at this. I actively use the "find in files" function. But there are situations when I again have to figure out what happens, for example, when expanding the old system or fixing the bug; in this case, it may just take an hour or more to research. This allows me to leave additional useful comments that I did not initially think about.
I remember that Threetoe (Tarn’s partner in game development) writes stories, and then you try to create a game engine in which they can happen. It still seems to me that this is an inspiring way to work. Were the stories you rejected too complicated to implement? Has it ever happened that the plot of one of them was completely repeated in the game?
Ha, I think, in a way, they are all too complicated. Character motivations, setting goals, etc. keep lagging behind how they happen in stories. However, this is still a useful process, because there are always lighter elements for generating history; in addition, we can approach the basic mechanics of the characters, even if we never reach it.
Wikipedia says that the version number of the game (now .44) shows how far you are from completion (i.e., 44%). What awaits Dwarf Fortress in the future? Do you have any forebodings about what will happen? What serious aspects do you have left to implement?
I am finishing the release with the villains, which will be released in the next few months. He should be pretty curious. Then we will implement the graphics and increase the usability of the game for versions on Steam / itch. Then we will improve the siege process and do some more work, and then move on to Big Wait. This is the largest restructuring and expansion in the history of DF. It will allow us to generate myths about creation and create fully procedural systems of magic, as well as open several windows for viewing different parts of the world, etc. This will be a great addition. Then there will be a release with property / laws / customs. After that, the order has not yet been determined, but we will work on the economy, ships and other important components that are not yet available. We still have a lot to do! We haven't even gone halfway to version 1.0. But version 1.0, the development of the game will not actually end ... perhaps, by the time of its release, we simply will not have much time left.
In games, there is a balance between plot and simulation, between a pre-written story that is in most games, and the creation of a deep world with a set of rules that allow many different stories to arise. I would say that Dwarf Fortress is one of the most serious arguments in favor of simulation. Do the characters at the stage of generating the world or during the game do something that even surprises you? Can you give any interesting / catchy examples?
Yes, this happens all the time! This is partly due to the fact that when you play, it is difficult to keep all the rules in mind. However, all my memorable stories are bugs, because I rarely have the opportunity to play the game for a long enough time, so from the point of view of the independently occurring gameplay, they are of little interest (even if they surprise me). Good stories can be found on forums, streamers, and so on.
The border between micro and macro: why did you make it like that, between what the gnomes can do themselves and what the player orders the fortresses to do?
This is a difficult balance, and it is not always easy to maintain, but at the moment the concept is that the player is the “official spokesman for the will of the fortress,” and the dwarves display autonomy, which should be present outside their official duties. This allows them to be actors in their own stories, which are the main source of the emerging (emergent) narrative. At the same time, the player should be able to control the main flow of his part of the game (in fact, this is not critical, but often more interesting than observing the simulation).
These two goals can conflict, and this is often due to the player continuing to enjoy the game - for example, if the emergency lever really needs to be lowered, then the task priority system can practically make the gnome do it, autonomously or not, depending on whether he should “know” about it or not. In the past, we had problems adding too much bureaucracy when, for example, we had a quartermaster dwarf involved in the delivery of equipment. But this system was too slow, prone to bugs and confused players. It is very important to think about how each individual game mechanics can add potential stories, and the quartermaster almost did not play any role in this.
What steps does the program take to build the world?
She allocates memory for the card. Then she chooses which pole she will have (for example, north, south) (or takes into account the parameters passed by the player). The Seed of the random number generator sets the basic values of the map fields (altitude, precipitation, temperature, drainage, volcanic activity, wildlife) for a grid of variable size taking into account various parameters (oceans, island sizes, other variability), and then the program fractally fills them. The temperature change depends on the poles, and the program selects points for the highest peaks. Here she makes the first pass to see how the process is performed, and tries to change the heights so that the map fits into the desired parameters. At this stage, if the world cannot be corrected, it is discarded, and everything begins anew.
Then the first derived field is set - vegetation - depending on the height, amount of precipitation, temperature, etc. The program checks whether the biomes correspond to the intervals specified in the parameters. At this stage, the heights of the mid-level are smoothed to create more flat areas, and volcanoes are placed in accordance with the field of volcanic activity.
Then we move on to the erosion and river stage. Small oceans are drained, the program finds the edges of the mountain slopes from which it can run test rivers. In addition, she has a camera on one of them so that the player can follow the process. A lot of fake rivers flow down from these points, breaking channels, if they cannot find a way to the sea. Too high heights are sometimes flattened so that the entire map does not turn into canyons. Ideally, it would be necessary to use types of mineral substances for this, but so far we are not using them. Lakes are grown at several points in the rivers.
The heights are again smoothed from the mountains down to the sea, and for peaks and volcanoes local adjustments are made. After completion of the creation of heights, the program makes adjustments to the amount of precipitation based on rain shadows and precipitation in the mountainous regions. Based on the height and precipitation, as well as the restraining effect of forests, temperatures are re-set, after which the program uses the new values to finally set the level of vegetation. Salinity values are set for the ocean and its neighboring tiles.
Having decided on all this, we can now find the boundaries of the final areas of biomes to give them names and personality. Here we also add geological and underground layers, although, as mentioned above, geological information should have been added earlier. Next, the final verification process for compliance with the parameters is carried out to make sure that we are not too deviated from what the player wants. Having finished this, the program generates the initial populations of the animal world for each region and sets the weather variables.
The story itself starts from this moment. Then civilizations and caves are located. It is rather difficult to explain what happens next, but the main idea is that a huge strategic game is simulated with zero players with rather arbitrary rules of moves and poor AI (but with thousands of agents), based on which history is written. The procedural generation of stories using simulation logging is a completely working approach, but it also has its drawbacks. This is a large amount of work, it is necessary to perform post-processing and study in order to find good points that you want to emphasize, and if the dynamics and mechanisms are not enough, then the result may be boring.
In dfWhat I like most is that the game does not contain any explicit hit point system, everything related to the strength and damage of the character is part of a complex model of body parts. What are the advantages of such a system? If someone decides to create something similar, then what obstacles can they expect?
This model creates more detailed plot moments, long-term consequences and provides greater system connectivity. It’s easy to go too far, and we really have gone too far in some places, at least at the current stage — some properties of materials are not used anywhere else. In addition, there are ways to mitigate the system, examples of which can be seen in games where, for example, there is a common pool of shutter speed / energy / hit points, but specific wounds arise either as a result of critical hits, or as the consequences of reaching zero or close to zero values in the pool. The correct system choice depends on the game. We strive to use numbers as little as possible, because numbers usually do not fit well with stories.
What does the main game loop look like?
Take for example the gnome mode. It starts by checking ads and reading autosaves, etc. Most of the rest of the cycle does not occur in every measure. For example, after every hundred clocks, the program checks assigned tasks and “strange moods”. Armies move around the world map. After every hundred clocks, the program processes the tasks given to the dwarves. This is a kind of invisible auction that is used to manage different conflicting priorities. Every ten ticks, the seasons change, which affects the weather and the map (both locally and around the world), as well as checking the development of plot elements (diplomats, sieges, etc.) and checking that the fort is still alive.
Then the program takes on what is done in each measure. Liquids move and other information of the map tiles changes (however, there are various optimizations so that every tile is not necessarily checked in each move; in addition, there are flags that allow you to skip whole areas of the map if nothing happened in them.) The movement of predators is updated and processed other “events” of the map, for example, active fires.
If the flag is set, then the wounded / thirsty / hungry gnomes who cannot take care of themselves receive an update. Dead gnomes “think” of their burial rituals so that these tasks can be assigned to others. Beings enclosed in cells and chains periodically update their thoughts and situations.
Then, if the creatures crossed the edges of the map, they are removed from it.
Every fifty measures the information of all taverns, temples, libraries, etc., depending on other measures, is updated. Supplies, also dependent on other measures, work similarly. Similarly, the creation of tasks for storage. Despite the fact that this process is complemented by various optimizations, it is still quite slow and at a certain point over 50 thousand stones can cause problems.
Every thousand measures, objects marked for deletion from the game are actually deleted, and the memory allocated for them is freed. This happens more often with objects, once every fifty measures, as well as checking the use of buildings (basically these are updates for wells and some other flags that need to be checked often.)
Next, we perform another update that works in every measure. Shells fired from the weapon are removed. Actions (from dancing and martial arts training to storytelling) are updated as needed. Dwarves and other creatures make decisions and perform their instant actions (moving to a neighboring tile, working in a workshop, etc.) - the main part of their AI (except for choosing tasks) is performed here.
Every hundred measures spoil items. Every measure of growth of vegetation is performed (even though there are many dependencies and flags.) If necessary, the state of buildings is updated in each measure and trolleys are moved. Advance on transportation routes. The temperature is updated (there are many optimization flags here, but so far this is still a rather slow process.)
Finally, the camera is updated following the creature the player is watching.
In Dwarf Fortress to display the world map tile is used on the basis of a grid - a simple and effective way of presenting. I noticed that there are many ways to draw tiles, and this depends on what the creature does or how it feels, how many elements there are on the tile, whether there is anything above it, whether the tile is flowing water or it is buried under a stone. When DF decides how to display the tile, what does it do? How did you optimize this process?
This is just a symbol (byte) with a few more bytes of color, so the system is not expensive at all and we can simply replace the selected solution before displaying it on the screen, and not try to solve everything at the same time, and for most tiles, just one earth / wall symbol is still enough. The program runs from bottom to top, in a sense, using the "height" (creatures are above objects, objects are above the earth), changing the decision from time to time. Nevertheless, despite the presence of various flags and auxiliary arrays, much more can be done. There is a small array of possible units that can be displayed in each tile, so the program can implement frame-by-frame animation that allows you to see each element on the tile, even when the game is paused.