Hi there.
Im new to these forums, and to the game itself. I first heard about Dwarf Fortress about a year ago, but was frightened off by the lack of visuals. But recently i got to know that the game employs procedural generation, so i just had to force myself to suffer through the ASCII 'graphics', and got hooked immediately. At first it seemed strange that such an impressive game has such poor presentation and handling, but now that i know DF is all written by a single programmer, its understandable, and im simply awed. Truly, a monumental work for a single person. I salute you, Sir Toady.
Still, mastering the interface and efficiently managing the fortress is a daunting task. Being a programmer, i immediately started thinking about providing new management options. Since the game ASCII visuals made it look like its simply a console output, the obvious thing to do would be to redirect input and output streams to another application, right? Wrong. After spending few hours researching the idea, ive discovered custom tilesets and the fact that its actually tiles rendered by Opengl.
Then i stumbled upon this:
Dwarves was written solely for PDCurses and was in that state for more than two years. OpenGL support was added only in January of last year (mainly for input reasons, since Curses input is iffy), and it simply copies the Curses display. There's a lot of hard-coded 80x25 assumptions, and also the fact that Curses displays one tile per cell is very important. Having things like a dwarf standing on a floor requires at least two tiles being displayed in the same cell, which requires the code to think differently about many things. It's not impossible, but it's time-consuming.
So i started thinking about overcoming these limitations with as little effort as possible.
Thus, heres an interesting idea i came up with.
---------------------------------------------------------------------------------------------
First off, there are three (probably obvious) assumptions about the way DF is written is that i make:
a) There is a two dimensional array (25x80) of integers that is used to tell OpenGL which tiles should be rendered. Each integer represents a single tile (character) - 97 for a, 98 for b, 125 for }, etc. Ill call this array the 'render array'.
b) The game loop can be reduced to this pseudocode:
GameLoop()
{
Logic();
Input();
Update();
Output();
Render();
}
Where:
Logic() - processing game logic/simulation, the logical state of game, game mechanics (the map and world, units, objects, orders, actions, movement etc)
Input() - processes keyboard and mouse input and manages interface logic (map, menus, cursor) according to user input and to game logic. so it actually handles the logic of input, menus, map, orders, and cursor
Output() - updates the interface and map state (basically everything that should appear on the screen) according to Input() (user changing menus ...) and Logic() (units moving around ...), and writes the current state to the render array
Render() - renders the tiles to screen using render array as a guideline
c) Input() and Output() are separate, independet and can be multiplied.
c) essentially means that it would be possible to have, for example, Input1 and Input2, each separately (but in the same frame) handling user input and interface, and Output1 and Output2, each separately writing the render array (to array1 and array2). Output1 would get the info on what exactly to write from Input1, and Output2 from Input2. Ultimately this is like having two separate viewports of the game, bar the actual rendering.
Now, the core of my idea is this modification of game loop:
GameLoop()
{
Logic();
InterpreterInput();
Output1();
Output2();
InterpreterRender();
}
Input1();
Input2();
The main concept here is the interpreter. It receives all keyboard and mouse input (instead of Input() ), but not just that. The interpreter is a full-fledged GUI and renderer, with windows, buttons, sliders, combo boxes and whatnot, and - of course - with fully customizable tile rendering. It doesnt replace the logic of current game input and gui, though. It builds on top of it instead.
Input1 and Input2 are outside the loop. Thats because normally, Input() needs to be called whenever theres any keyboard or mouse input, but now the InterpreterInput() handles all keyboard/mouse input. But it doesnt handle the underlaying, original gui, which still needs to be processed. This is achieved by selectively calling Input1() and Input2() from InterpreterInput() and passing parameters generated by InterpreterInput() as input for them, essentialy faking keyboard/mouse input.
Next up is Render() replaced with InterpreterRender(). Theres no need for the old OpenGL 'ASCII' renderer, since the new interpreter is going to handle visuals. It takes both render arrays from Output1() and Output2(), and processes them into its own gui and map.
For example, if you click in the interpreter on designations button, the first input function is called with 'd' key event as a paremeter sent from the interpreter. But if you click on units button, the second input function is called with 'u' key event. Similarily, in the interpreter there are two menu panels, left and bottom one. Left one receives render array from Output1() (designations), bottom receives array from Output2() (units).
Heres a more sophisticated example. Imagine you have three Inputs and Outputs. Normally that would be like having three windows of DF that work on the same instance of game, with separate gui and input.
First 'window' is this:
It is used for map display; all map scrolling and view options input (events from interpreter like pressing button) get sent there.
Second and third one:
Second is used for all operations (so digging, building placement, orders ...). However, only the middle part is passed to interpreter as output, the left tab serves only for input purpouses, and the actual visuals of operations (ie. placing a building) are rendered by the interpreter on map display from first window. Output from middle part is used as info for interpreter's 'operation' interface.
Third is used for interpreter's 'look around' interface (obviously, 'k' look around from DF). So its in permanent look around state (by sending 'k' as key event to it and not giving any other input except cursor movement) and only middle tab is sent as an output. The actual cursor is, of course, renderend on the map from first window.
I am aware that this idea of mine seems extremely and unnecesarily complicated, but there are a few significant advantages of this approach:
- Despite its apparent complexity, it is actually quite easy and doesnt need too much work to implement, which is an important factor, from what ive gathered. Copy&paste Output() and Input() changing some names so that they dont duplicate, remove pause from menus (when you open up any menu, the game pauses), provide an entry point in Output() for input events from interpreter (could be as simple as changing every if(keyboad.keydown()==97) to if(InterpreterKeyEvent.keydown()==97) etc), and make the render arrays available for interpreter
- The only real work involved is the actual interpreter (so renderer, gui, interpreters logic and input). It has a great potential to be developed by a third party (see next point) and, providing its not too complicated and documented well enough, it should be easy to add new features during further game development (for example, a third party writes the core framework and creates example interface, but if need be, is expanded by the game developer).
- The interpreter is pretty much separate and independent from the main game guts, which means it can be easily written and maintaned by someone who has no knowledge of DF source. It could also be external, so the game and its development wouldnt depend on it.
- It can be as comprehensive and advanced as one wants, or as simple as just two DF windows displayed and handled simultaneously but independetly.
---------------------------------------------------------------------------------------------
Or, if all else falls, just 'downgrade' DF to a console application, allowing for redirection of input and output stream. Such a simple change would be enough to write utilities that greatly improve interface capabilities.
In any case, i would like to inquire about the possibility of actively helping with development of Dwarf Fortress, and offer my help.