Saturday, 5 November 2011

Core-UI split: user input


A lump of slate
Here are my current thoughts on how to move forward with the Core-UI split.  At the moment there aren't really layers - it's more like a lump of slate that's starting to crack in two but isn't quite there yet.  The biggest single issue that I see right now in moving towards that goal is user interaction, and that's what this post covers.  This post was written while I was working out the issues, so I hope it makes sense to other people!

At the moment, there is a game command layer which is the intermediary between the UI and the core.  Roughly speaking, the core runs in a loop on a queue of commands.  If the queue is empty, it passes control to the UI and asks it to get a command.  The UI then gets some input, works out what game command that corresponds to, and pushes that command onto the game stack.  When the queue isn't empty, the game just runs through it, executing every command it finds until it runs out of commands to process.

This picture is complicated a bit by the presence of macros.  At the moment, macros are implemented at the UI level as a queue of keypresses.  So there are two queues operating at once - one command queue and one keypress queue (actually, there are three queues - the terminal code keeps another keypress queue).  To illustrate, say we have a macro P:

P = p1bt

And let's start with an empty command queue.  This is the flow inside the game:

Core: Queue is empty -> ask UI for keypress
UI: Player presses P -> put p1b*t into the keypress queue
UI: Process 'p' -> UI command calls prayer book selection code
UI: '1' selects book inscribed @p1 -> select prayer menu
UI: 'b' selects prayer b -> put "pray book:1 spell:b" into the command queue, return control to core
Core: Checks over command to make sure it's got all the data
Core: Calls associated command function - do_cmd_pray(1, b)
Core: 'b' requires a target so it prompts for one
UI: 't' popped from keypress queue, ends prompt
Core: execute prayer

The problem here is that the game core isn't given enough information up-front to execute the prayer without user input.  So the question is: what do we have to do to get the command layer running without user input?

First off, there is a problem at the moment in that the game command queue handling code sometimes fills in the paramaters given to the functions that execute those commands.  So, for example, when the game core comes to execute an 'open door' command, but no direction is provided, it will check to see how many doors can be opened around @.  If more than 1, it will prompt.  This is bad layering, and the way to fix it is to make the game command code refuse to run incomplete commands, and put that logic in a level a bit higher up.

The bigger problem, though, is the one in the prayer example.  Different commands require different input, but not based on their command type (aim, read, pray, etc) but on what the effect the object they are used with will invoke.  It doesn't matter whether you're making a fireball from a want, spell or rod - they all need a direction.  Enchant Weapon requires an item regardless of whether you're reading from a scroll or a spellbook.

To make this work, we evidently need close tie-in between the command and effects code.  Effects basically need to be specified in such a way that their input requirements and implementation are coded up separately. How to do this in C is quite beyond me at present - while many effects only need a target, some portion want to select an object, which requires a prompt, a filter function and an error message if the effect can't be invoked because there is no appropriate object.  All this needs to be specified in some declarative way so that the UI, game core, and effect code can all access and use it.

Any help appreciated!

After sorting out this (very sticky) point, I think we're pretty close to a proper UI-Core input split.  One useful way of enforcing the constant that the core layer doesn't ask for input would be to add assert()ions in inkey() when a "in the core now" flag is set, and that would help find the remaining instances pretty quickly.  Of course, there's still some output issues - I'm sure some of the core code writes to the screen directly - but they could hopefully be sorted out pretty quickly.

Macros

The ambition, after making all game commands run without user input, is to rid ourselves of the second key queue inside the UI layer, and we can do this by rewriting macros to be a string of game commands sent to the core in one bunch.  So instead of:

\e\ep8d-a\e\ep8d-a\e\ep8d-a\e\ep8d-a\e\ep2f\e\eR\r

Your macro would look like:

cast "Enchant Weapon" item:floor:1
cast "Enchant Weapon" item:floor:1
cast "Enchant Weapon" item:floor:1
cast "Enchant Weapon" item:floor:1
rest