|License:||Free for Non-Commercial|
|Operating System:||Any SDL Platform|
In the mid 1990s, Raven Software licensed the DOOM source and created two fantasy games. Hexen was the later of the the two and continues the story from Heretic:
While you were battling the evil forces of D'Sparil, the other Serpent Riders were busy sowing the seeds of destruction in other dimensions. Now, three brave souls have sworn to crush the evil regime that threatens to destroy the world forever. Find Korax's stronghold, destroy him and restore order in the physical world.
Hexen, like most commercial programs, was never intended to be released in source form. Prior to Id Software's release of DOOM, releasing the source to old games was seen as madness. After Id, a handful of companies released some code. The pressures of real world concerns gives the source a different feel from open source projects.
On initial release, Raven Software made a self-extracting archive on the internet. They quickly realized that not everyone could run the self-extractor and released a zip file. Still they left the end user license agreement as a Microsoft Word file which is rather annoying. Although Raven has been fairly supportive of modifications and ports (1), the only email address in the source appears to be for legal questions on the license. This is not what you typically find in an open source project.
Since the official source is missing the sound library, this article focuses on Karl Robillard's port to SDL. I did look over some of the original source to determine how far the SDL version has progressed. For the most part it seems to have kept the original code base. Even so, the Hexen code was not built from scratch. They licensed the DOOM source from Id to build Heretic and Hexen.
When looking at source code, I usually start with header files since the
structures and function prototypes can give a good overview. In this case
I discovered multiple prototypes for some functions. For example
I_RegisterSong has a prototype in i_header.h and
i_sound.h. After a quick search I discovered six unused header
files(2). In the previously mentioned case,
i_header.h is the unused file even though it contains a short
description for each function. The code is fairly clean and well
In h2def.h, the thinker structure is defined. The thinker is a linked list of function pointers. The functions decide the actions for all the game objects, from the bobbing health vial to the shield-carrying centaur. From this observation, the logical assumption would be that each monster has it's own thinker function. That's not the case.
All monsters share a single thinker function
Objects such as doors and raising floors have separate thinker
functions. So how do the monsters have different reactions? Hexen uses
a technique known as a state machine.
A state machine is a fairly simple concept and and easy to implement; it can also make a reasonable opponent. The machine is given some initial state. Every turn the machine looks at the state and determines what the next state should be. A simple monster could have four states: look for player, first move towards player, second move towards player, and attack. Your first thought would probably be to run everything in order and loop infinitely. Alternatively the creature could look for the player once, followed by an infinite repeat of move and attack until the player dies. The first implementation results in a simple grunt. The second will hunt the player to the ends of the earth if needed, ignoring all other opponents. A good state machine is obviously more complex and includes some randomness to prevent total predictability.
In Hexen, the states are defined in info.c. Each state includes the
number of clock ticks spent in the state, sprite and frame information, a
function to run, and finally what state to go to next. The action function
is what actually does the appropriate task and may override the state
information. For example
A_MinotaurChase causes the minotaur
to race after the target. If the minotaur is within range, it will skip
the next state and attack immediately. There are over two thousand states
in the game.
In general I'm not a big fan of first-person shooter. By adding conversation with the creatures in the game you could greatly expand the possibilities. The current conversation technique has a single key per player and communicates across all distances. Ideally, conversation should involve only those in the nearby vicinity. As an initial test I ignored the talking problem and added 'm' to speak directly to a minotaur summoned by the player.
The minotaur could have been easily turned into a robot that does exactly what it is told. Or a simple script could have been written. Instead, I decided on something a little more interesting. For my college artificial intelligence course we had to implement a simple version of the classic Eliza program in Lisp. A quick conversion of the function to rephrase patient's statements into a question, and the minotaur came to life. Well, he did up until the crash anyway.
message buffer is eighty characters long. As part
of turning the statement into a question a beginning sequence of up to
twenty-eight characters may be added. This limited the player to fifty
characters which still seemed reasonable. As it turns out though the screen
display can only draw thirty-eight characters, after which the program
crashes. For the time being I just chopped the response. A better
implementation would be to store the words that won't fit and send them
Unfortunately sending the messages later wouldn't help very much because
the player is limited to a single message on the screen. To make
conversation truly useful in expanding the game, the player would need to
view more than a single line of text at once. Additionally, after moving the
message variables from
discovered a player does not have a defined monster object at creation.
This may prevent the player from receiving messages in some circumstances.
Low memory computers may have problems with the increased size of the
For anyone considering further enhancements I noticed that monsters contain linked lists to other creatures in the same sector and the same block. Depending of the size of sectors and blocks they could perhaps be used to send messages to everyone in an area. It would lead to break points where you can't speak to someone just a few pixels past the sector break, but it wouldn't require much additional coding. Here is the current patch.
Even though there were several flaws that prevent my Eliza patch from being
a complete success, prototyping conversation proved to be a fairly simple
addition. There was some additional hackery I found such as storing a
pointer to the summoning player's
mobj_t in an integer. Still
the programmers at Raven managed to exceed my expectations on Hexen. Which
is perhaps a little unfortunate for those who come afterwards.
(1) There are several other ports available. I tried to chose one that remains close to original source.
(2) Unused header files: ct_chat.h, drcoord.h, dstrings.h, i_cdmus.h, i_header.h, and vgaview.h.
(3) In my initial overview, I stumbled across two functions
with nearly the same functionality,
strupr in w_wad.c. They both convert
strings to uppercase in a slightly different manner but I didn't get a chance
to explore why.
Back to OGS