Sunday, 30 December 2012

My Year of Game Development

TL;DR? It's new years, and the first release of lanarts in 3 months! 
Download: here!
Github link

I believe I've grown a lot as a programmer in the past year. My knowledge of version control has expanded greatly -- from initially using SVN as little more than a file drop. I've moved from using windows exclusively to using linux exclusively. I started working at Red Hat this summer and learned much about real-world code (for the curious, I'm on the OpenJDK team, working on icedtea-web).

I decided to write a retrospective on my past year of game development on lanarts, I hope you enjoy it.

... Though to be honest, I didn't feel I could do it justice without some additional background pre-2012.

Pre(r)amble    Game Maker days

http://www.64digits.com/users/ludamad/CirboticsGIF4.gif

A game from my Game Maker days


I started programming by making games in Game Maker, although I have sworn off the program years ago. Since then I found that programming was fascinating in itself, and perhaps more so without annoying user interaction to worry about. In that time I did a great deal of hobby C++ programming, and some Java.

I started to enjoy working on large projects, namely a python-like scripting language. While very interesting, this proved to be unremarkable to my friends. So I thought, developing games again would be nice.

Summer 2011    From zerglings to witches and wizardry

My roommate was interested in collaborating on a project, and so we set off to make an RTS that was centered around LAN play. Lanarts. Somehow this idea got dropped, and instead we opted to make an RPG. The name stuck.

The first thing I did was toy around with level-generation code. It was quite simple, just plop down some rooms and tunnel between them randomly (backtracking on failure), but it worked well -- the code in place still stems from this.

After that I sought out find tiles to graphically display this dungeon, and found the rltiles tile-set, and subsequently the rogue-like Dungeon Crawl which uses them in a heavily modified form. Much distraction ensued! Soon me and Pat were playing Dungeon Crawl more than coding. However at the end of all the dungeon-diving my vision was clear, to make a game that could closely resemble a co-op roguelike as possible. 

Fall Semester 2011
    The foundations of a game

I used SVN & CMake in a research project during a research position I had during summer 2011, so they were natural choices for me to use in my game. Pat was using linux exclusively, while I was on windows, and this was a very good thing to tackle from day 1. In retrospect I wish I had also handled networking issues from this point as well.

Work on the engine progressed. I had the foundation of a game engine going in C++. OpenGL drawing, game actors acting in an event loop, and a rather complicated actor-storing data-structure that, while tricky to write, I still use and has worked very well.

January 2012    Movin' to git

Why git ?

I was fed up with using TortoiseSVN, and Pat convinced me to get a google-code account and try out a better version control system.

Features at this point
Basic .BMP loading, OpenGL drawing, font loading with FreeType, a minimap, collision system, field-of-view, keyboard controls.

No gameplay ... yet!

February 2012    It's alive!

Initial Development

The game progressed with some additional sprites hard-coded into the game, which now could be any image format and not just .BMP. A single enemy was added, able to path-find to the player. It could be killed with a simple ranged attack. A fog-of-war outside of the field-of-view was implemented; the map no longer starts revealed.

March 2012    It's a game!

Adding Gameplay
The game progressed by leaps and bounds this month. Enemies could attack, and 4 different kinds of enemies were introduced (all still in the game!). The level generation was tuned further and levels were given distinct layouts. An inventory was added, basic items such as HP & MP potions, as well as basic stats such as experience points and levels, and damage. No death animations were present yet, and melee attacking was preformed by simply walking into enemies, much like typical rogue-likes.

April 2012    It's an online game!


Taking it 2-Player
The game became even more game-like, with weapons, scrolls, and an additional spell (in addition to blinking and fireball which were already in). Monsters were no longer melee-attacked by walking into them, instead requiring a key press. Enemies get stronger by a fixed formula the deeper you go. Online was implemented! It was fraught with problems - crashes and synchronization issues - but the game could be played with 2 people!

May 2012

Online was further stabilized, projectile items added, additional tilesets, a game-chat bar that features notifications. Many new enemies.

June 2012
    First blog post!

Overhaul of stat system to one used currently, many new enemies including a set of animals, and two bosses. Many UI improvements, addition of weapons such as bows, early prototype of fighter class. Added visual representation of resting-system. Enemies travel in groups nicely thanks to the RVO2 library.

July 2012



Fighter class fully added, with separate sprite, and berserk spell. New tabs added to the interface. Stats displayed more nicely. Arbitrary numbers of players were added. Moved to github, where the code currently resides. Mostly fail at attempts to get additional developer interest.



August 2012    ... More than 2 players!

Shops were added. New archer class hastily added, was quite underpowered. Continue to look for help on the game, without much luck.

September 2012   ... ARRP, last official release (until today!)



The game was released for the ARRP. See this playthrough! The equipment system was redone with many new equipment slots. The main-screen was redone to allow for setting IP and connection port cleanly without fiddling with resource files. Work started on a new game, Carny Death Peddlers, with and old partner-in-crime, REZ, from my Game Maker days. The ability to work with a brilliant pixel artist was too enticing -- even if the project was forced to use Game Maker...
 
October 2012
    ... And Carny Death Peddlers is finished!

Frantic game development, in time for Halloween!
The game was successfully finished within a month-and-a-half for a Halloween-themed game development contest. I think it's obvious that this is a very small timeframe to complete a game. The dedication that my artist partner REZ showed for completing the project kept me on-task. There was definitely many stressful periods but I've never worked on a game with someone with such unified vision, it was awesome! The game has since been featured on several gaming websites such as GameJolt, and has ~1000 downloads on 64Digits.com. The game features great original music by Glock&Mr8bit

 November & December 2012    ... A needed break, and then back at it

Engine improvements
After the release of the last game I was a bit burned out. However, creating a full game in such short amount of time was inspiring to reapply my efforts towards polishing Lanarts. Glock&Mr8bit and REZ both agreed to help with the game. Unfortunately, REZ soon became busy with his own projects. Glock&Mr8bit worked away at producing music, and I worked on improving the engine in various ways, such as being able to play sound effects and music, and a better Lua framework. Still looking for additional developers.

Download: here!

This release features:
  • Lots of balancing work
  • Lots of new items, with new items added for practically every equipment slot.
  • Two new enemies, a centaur who keeps his distance, and a hydra whose head count represents its remaining HP (a future change will make these more like the 'cut off one head two grow back' hydras of legend).
  • A new fighter ability - 'Power Strike', an attack that hits multiple enemies in an area with your weapon, and also knocks them back with a temporary stun. Gained at level 3.
  • Interface improvements, better auto-equip logic when switching equipping projectiles & projectile-using weapons. Spells that are typically used once and then switched to melee no longer default to melee when their key is held. You are now able to use spells without first switching to them (previously two keystrokes were needed to use a not-selected spell).

Tuesday, 30 October 2012

Carny Death Peddlers is Released!


A classic beat-em up.

Download Page: here
Download Now


I mentioned I was on a break from lanarts. I was not slacking! I don't think I could keep myself from coding if I tried.


Features:
  • Amazing animated pixel art by Clay Bullard
  • Catchy tunes by Glock & Mr8bit
  • Fun, classic beat'em'up gameplay with a fair bit of polish
The game is 2 stages long, with 2 bosses. Lots of hard work was put into getting this game done in a small timeframe (~1 month and 2 weeks), I hope you enjoy!

Note: This is a Windows-only game, unforunately. However this game seems to work smoothly, albeit without sound effects, under WINE.

From the README:
Welcome to Carny Death Peddlers, a classic-style beatemup with a simple plot.

Made by:
    ludamad aka Adam Domurad - programming, sound effects
    REZ aka Clay Bullard - art, content design
    Glock and mr8bit - music, testing

Saving:
    Saving is automatic at the start of every battle.
    You know when a battle starts because "FIGHT!" text will appear.
    Progress before the next battle starts will not be saved.

Controls:
    s - charge punch when held, normal punch
    d - push
    z - backslide
    x - block
    space - use reserve item

    m - mute
    p - pause
       The following controls were not documented in-game (Unfortunately it cannot be modified, they will probably be added in an update):
    enter - skip text
    F4 - toggle fullscreen

Screenshots:

Saturday, 15 September 2012

Lanarts ARRP Release!



Screen shot showing off redone configuration screen
Download: here

Another release in a short time - this one focused on the setup menu mainly.
This release aligns with the ARRP: 
http://roguebasin.roguelikedevelopment.org/index.php/2012_ARRP
Here are the changes since a few days ago, see also the last post for recent changes:
  • Made the last boss harder
  • Made less difficult enemies spawn in later levels
  • Simplified startup screen into a single 'start' button, press enter to continue. Escape no longer continues.
  • Speed setting added to start menu, I recommend default setting. 

Tuesday, 11 September 2012

Lanarts Update September 11th, 2012





Screen shot showing off redone equipment system
Download: here
Recent changes:
  • Lots of new types of equipment! In addition to the previous single armour slot there are now two ring slots, a glove slot, a boots slot and a helmets slot. Items spawn and can be found in shops for each equipment type.
  • Redone equipment system where equipment shows in inventory.
  • Greatly improved the code for top-bar descriptions of items and things, now guaranteed to be formatted nicely as small as 640x480 (This is the smallest I expect the game to be run, use settings.yaml to change resolution if need be, it defaults to quite large).
  • Equipment tab now shows all new equipment slots and can be used to see which slots must still be filled.
  • Some new weapons, thanks to gigimoi.
  • A few slime enemies and a spider added, thanks to gigimoi.
  • Enemies no longer stack in tight (1-tile) corridors! This has been long overdue (it was hard to think of a good way to do it for technical reasons) and makes tight corridors a lot safer. (For the curious, previously enemy's more or less ignored collisions with each other when they were in corridors).
  • Last boss made beatable again... :)

Friday, 31 August 2012

Lanarts Update August 31st, 2012




New archer class and tiles (credit to white_noise from DCSS forums)
Download: here
Recent changes:
  •     Option to force sync in out of sync games - press F5 and you will essentially send a save file to everyone
  •     F5 to save game/F6 to load game for single-player games
  •     Improved robustness when playing >2 players
  •     Bunch of bug fixes
  •     New archer class - heavily work in progress. Has bow-oriented abilities, such as being able to hit multiple enemies with a single arrow, faster shooting with level-ups, and a spell that only works with a bow.
  •     More tiles, variations for player class tiles
  •     Balance changes as always

Monday, 20 August 2012

Lanarts Update August 20th, 2012


New tiles and statue features
Windows binary snapshot: here [Updated 21/08: Fixed a bug that caused crashes when doors were seen]
Recent changes:
  • Rewritten net code, uses SDL_net instead of asio. Dependency on boost removed. 
  • Support for >2 players
  • Much faster online play, increase frame_action_repeat in res/settings.yaml as desired and you should get arbitrarily faster play for a trade-off of responsiveness of controls.
  • Experience always shared in online
  • One synching issue found and fixed, more still exist sadly. Next release will include a button to force a synch as a practical measure.
  • Some tile updates, stairs & wall tiles picked from Dungeon Crawl
  • Balance changes as always

Tuesday, 7 August 2012

Lanarts Update August 7th, 2012


Shop interface
Windows binary snapshot: here
Recent changes:
  • Shops
  • Mage's fireballs have significantly less cooldown
  • Mages have a bit more mana
  • Balance nudging
  • Equipment tab
  • Auto-use potions, you can turn this off in the config tab (click on the gears in the sidebar)

Saturday, 28 July 2012

Lanarts Update July 28th, 2012



Class select screen and fighter on floor 5
Windows binary snapshot: here

Recent changes:
  • Fighter with berserk spell
  • Hardcore mode = no respawn
  • Class select screen

Monday, 16 July 2012

Lanarts Update July 16, 2012


A screenshot showing off the Monsters Seen tab.
Daily development on lanarts continues. 
Active development taking place on https://github.com/ludamad/lanarts as mentioned last post.

Windows binary snapshot: here

Recent changes:

  • Replay system - play back and saving of replays
  • Better menu - but not by much. Now you can chose to run a single player game, make a replay of your run, play back a replay, and host server/connect to server.
  • Two new spells, one gained at level 3 that does damage over time and weakens enemies, and one gained at level 6 that damages all enemies in vision. Both are extremely subject-to-change. Blink has been made a normal spell which brings me to the...
  • Complete overhaul of the control scheme, along with lots of hard-code being removed. Controls now use yuiop to choose spell slots 1 to 5. Holding the same button after choosing the spell will cast the spell. H now always uses your weapon. J/space/left click use the last used spell/weapon. Mouse wheel & q/e & < > are available to toggle through spells. Using items remains as numbers 1 through 9.
    Currently I have a lot of variations to see whats comfortable.
 Milestones:
  • LAN play much more stable now - known issues still exist, but occur much less often. A LAN playthrough to the end of the game (11 floors + last boss) has been done, for the first time.
  • First game over the internet that wasn't horrendously slow ! Hopefully incremental progress will render this playable while a better network model (lots of work) is done. Update: Followed up by an even more decent game. Things are looking good here.
  • Spells are completely defined by lua + yaml now, this will greatly speed up progress in this area. Hopefully there are people more inclined to contribute via scripting.

Thursday, 5 July 2012

Lanarts Update 05/07/2012




A screenshot showing off the Monsters Seen tab.
Daily development on lanarts continues.

News:


Windows binary snapshot: https://github.com/downloads/ludamad/lanarts/lanarts-05-07-2012.zip

Recent changes:
  • Tabbed side bar interface with inventory, equipment (TODO), spells, enemies, config (TODO)
  • Hover over descriptions for many game elements. Available when hovering over inventory, action bar, and new spells tab
  • New control scheme (on by default for now) where moving up/down stairs & picking up items merely requires you to stop moving
  • Balance changes continue
  • A few new enemies, giant bat & giant frog.
  • Melee weapons much faster

Saturday, 30 June 2012

Taming Program Complexity

Fast and Furious vs Built To Last

So I've always wanted to write about programming, and suppose this blog is as good an avenue as any.

Programming has always interested me in its incredible depth and complexity. I've recently started thinking about, as my project grows rather large, effective ways of managing complexity. It seems that the way people think of programming depends on thresholds of program size and scope. Even worse, in extreme cases, ordinary techniques tend to fall apart.

Consider two sides of the spectrum:
  1. A hackish throw-away script written to bang out a result, where rigorous programming techniques would probably slow you down
  2. A project written and evolved over the course of several years
The programming styles acceptable for these two projects seem to be at odds. In the case of a quick script, using a dynamic language can allow you to quickly type out code that you immediately visually confirm with some print statements, and build up to your result. Or you just type it all out as you feel it ought to work and let the program tell you otherwise. Either way, this is programming in its full glory. Quick, dirty, and way more effective than any manual approach to the problem. You can promptly toss out your code, and you'd still have saved time.

It takes no argument to deduce that this approach is not how you would handle a program maintained over several years. An important distinction is that in the case of a one-time script, you would need only to handle an instance of a problem. In a program that ought to stand the test of time, numerous corner cases present themselves, and must be dealt with methodically. The maintenance of the code base thus presents a challenging problem. If you write the code in a quick and dirty fashion through-out, you'll notice something disturbing. You will start with a simple elegant solution for a large subset of the problem, and then as corner cases and error handling is added for robustness, the code will swell with noisy code. Your essential solution to the problem at hand will be lost in details.

Code Always Changes
Chances are, if your one time script was truly successful and useful, it will be granted an extension of life. This extension of life can easily bring it to the boundaries of what it was intended to do. Vital to handling program complexity is to gracefully transition code from a prototype-like phase that solves most of the problems, to the point where it handles gracefully any number of conditions that can occur.

Consider a piece of code that began its life in a simple script:

function  sanitize_file(filename):
    file = open(filename)
    file_lines = []
    for line  in file:
         do something to line
         do another thing to line
         do yet another thing to line 
         if function1(line) 
and function2(line): 
             append(file_lines, line)
     for line  in file_lines:
         do something to line
         do another thing to line
         do yet another thing to line
         write_to_file(file, line)
    return success


This solves the original problem just fine, and elegantly enough. This is primarily because, there was only one instance of that problem, and it could be thus reasoned that the code was sufficient. Now, after writing this code, you find it very useful and are going to now adapt it to a larger project.

Lets say the code is simply dropped in, and then as the code evolved, additional features for robustness were added, as well as a number of explanatory messages of failures. Some time later, you look back at the code you wrote, and see it has become:

function  sanitize_file(filename):
    file = open(filename)
    handle file didnt exist:
         print("Invalid file!")
         return failure
     for line  in file:
         do something to line
         do another thing to line
         do yet another thing to line 
         if function1(line) 
and function2(line): 
             append(file_lines, line)
    handle error reading lines:
         print("Error reading file, possibly too large for memory!")
         return failure
    file_backup = create_fileconcat(filename  ".backup"))
    handle file already exists:
         print("Could not create backup file, already exists!")
         return failure 
    copy_file(file, file_backup)  
    handle write error:

         print("Could not write to backup file!")
         return failure 
     for line  in file_lines:
         do something to line
         do another thing to line
         do yet another thing to line
         write_to_file(file, line)
    handle write error:
         print("Error writing file, reverting to backup!")
         copy_file(file_backup, file)
         return failure
    delete_file(file_backup)
    return success 

The Death of Innocence

Code that is maintained over a long time inherits a substantial amount of code that is added to handle corner cases. The main issue with this is that while the code originally had a simple premise: taking a file and overwriting its contents with a sanitized version, it is now buried in details, and it is unclear which of those details relate to the actual problem. 


Consider for the sake of argument that to end users version 2 is much better, and it saves them from losing a lot of data. We are in the unfortunate position of no longer being able to believe that the original version sufficed, and now must accept the added complexity into our lives.

Simple-minded Code

How do we approach making code fit for a large project? More importantly, as not everyone writes large projects, how do we approach making this code fit for human consumption?

Often times when something is hard to understand or reason about, it attempts to do too many things. Even worse than doing too many things, is doing too many unrelated things. As hard as it is to reason about a path-finding algorithm, for example, it is much more complicated to reason about a path-finding algorithm that also draws and takes user input.


It is thus important to separate a piece of code's intent from its implementation. Many programming techniques such as classes, interface types, procedures and modules aim to handle this problem.

The simplest, and most ubiquitous, is the procedure. We can stand to gain by isolating the implementation of the code, from the intent of the code, with separate procedures:



function  sanitized_line(line): 
    do something to line
    do another thing to line
    do yet another thing to line
    return  line

function  prepared_for_output(line): 
    do something to line
    do another thing to line
    do yet another thing to line
    return  line
function  line_isnt_comment(line):  
          return  function1(line) and function2(line)

function  sanitize_file(filename):
    file = open(filename)
    file_lines = []
    for line  in file:
         line = sanitized_line(line)
         if line_isnt_comment(line):
             append(file_lines, line)
     for line  in file_lines:
         write_to_file(file, prepared_for_output(line))
    return success

This version of the original logic, while longer, is both clearer and more re-usable. Units of logic are abstracted into appropriate blocks of code. Especially useful for large projects is the ability to change the implementation of these components without changing their intent, the logical extension of this is the once-and-only-once rule:

Any functionality should only be encapsulated only once in the code. Any suspiciously similar blocks of code should be considered candidates for extraction into procedures. Any suspiciously similar types should be considered candidates for sharing components.
I would say equally worth noting are the explanatory function names, made as long as they need to be. I believe this to be a very crucial practice for code maintainability and can justify it simply: 
Comments are hard to constantly write and maintain, and any function name that is not clear in its intent necessitates many comments to keep the code understandable.
In the end, it is far less typing to have long, explanatory, function names. The code is in a sense, self-documenting.

(Of course, these function names should be even more descriptive of their intent, if we exactly decided what 'sanitizing' a file meant here)


Higher level thinking

For our last code example, we conveniently forgot about all the error handling that we had just come to terms with as necessary. To properly tackle the next problem we must first ask ourselves, what errors should this procedure really worry about? What errors do not belong in this procedure?

For one, the procedure makes no sense unless a file really exists. Secondly, the procedure tries to hide the fact that a backup is being created, but this is not quite achieved. So we shall remove the burden of creating a backup file from the procedure itself, and make the interface as follows:



function  sanitize_file(input_file, output_file)

We can maintain our previous interface by adding a wrapper function:

function 
 sanitize_file_by_name(filename): 
    file = open(filename)

    handle file didnt exist:
         print("Invalid file!")
         return failure
    file_backup = create_fileconcat(filename  ".backup"))
    handle file already exists:
         print("Could not create backup file, already exists!")
         return failure

    copy_file(file, file_backup)
  
    handle write error:

         print("Could not write to backup file!")
         return failure 
    was_successful = sanitize_file(file, file)
    if not was_successful:
         print("Error in sanitize_file, reverting to backup!")  
         copy_file(file_backup, file)

    delete_file(file_backup)

    return  was_successful


The advantages of this organization are clear, the error handling for opening files occurs in an isolated function that actually concerns itself with opening these files. As well, sanitize_file's implementation is free from details such as the name of the backup file, and the detail of deleting the backup file on successful completion.
Let us now combine our results:

function  sanitized_line(line): 
    do something to line
    do another thing to line
    do yet another thing to line
    return  line

function  prepared_for_output(line): 
    do something to line
    do another thing to line
    do yet another thing to line
    return  line
function  line_isnt_comment(line):  
          return  function1(line) and function2(line)

function  sanitize_file(input_file, output_file)
    file_lines = []
    for line  in  input_file :
         line = sanitized_line(line)
         if line_isnt_comment(line):
             append(file_lines, line) 
    handle error reading lines:

         print("Error reading file, possibly too large for memory!")
         return failure 

    for line  in file_lines:
         write_to_file( output_file prepared_for_output(line)) 
    handle write error:

         print("Error writing to file!")
         return failure

    return success

function 
 sanitize_file_by_name(filename): 
    file = open(filename)

    handle file didnt exist:
         print("Invalid file!")
         return failure
    file_backup = create_fileconcat(filename  ".backup"))
    handle file already exists:
         print("Could not create backup file, already exists!")
         return failure

    copy_file(file, file_backup)
  
    handle write error:

         print("Could not write to backup file!")
         return failure 
    was_successful = sanitize_file(file, file)
    if not was_successful:
         print("Error in sanitize_file, reverting to backup!")  
         copy_file(file_backup, file)

    delete_file(file_backup)

    return  was_successful

A lot to bear - a single function was made into 4 functions. However, very importantly, the functions where the actual important details reside are quite simple. Indeed, if we want to change the backup behaviour, we change sanitize_file_by_name, if we want to change the details of the pre-processing step, we change sanitized_line, and so on.

While it may seem daunting that 4 functions were created, one must note that any changes that have to be made will very likely only have to be made in one of these functions. Thus we have greatly reduced the amount of code we must actually look at to solve any maintenance problem. As well, the individual functions can be reused for purposes that were not originally envisioned, thus providing more benefit to a larger project.

The greatest complexity occurs from interaction of program components, not from the components themselves. Keeping the interaction between program components as straight-forward as possible makes it far easier to manage change in code.

Wednesday, 27 June 2012

LAN with lanarts



Windows binary snapshot: download

It's in the game title, so I'll describe how you can play lanarts over a LAN. 

First some notes:
  • The same mechanism works over the internet ... albeit horrendously slow. I wouldn't bother attempting this until the issue is solved completely with some sort of client side prediction. There is nothing about these steps that is actually unique to a LAN though.
  • The game has been known to get out of synch sometimes. The problem seems to be worse on different architectures, so best results would probably be on as close as possible OS & architecture (eg 32 or 64 bit) as possible.
  • More than 2 player, while not hard to do, has not yet been in demand. This is therefore for 2 people for the time being. 
So, here's how its done:
  1. Go into the folder where you have lanarts extracted (if you are building from source, then go into the source folder), and open the file res/settings.yaml
  2. Edit the network settings to be something like this:
    #Network settings
    connection_type: host OR client
    ip: <Enter host IP here>
    port: 6112  
     Please note that you should use a local IP for LAN purposes. You can arbitrarily choose one person to host, one person to be client. The ip field is not required for the host.
  3.  Have both players start the game. The game attempts to connect once you get past the menu. The host should hit 'start' first, and then wait for the client to connect.

    Both players should now be connected and playing! Synching issues aside the game plays quite well and is quite fun to play co-operatively. This will be a major focus of future development, as the aim is to create a fun co-op RPG.

Saturday, 23 June 2012

Lanarts Development




A screenshot featuring recently added animals & tileset.
Added sprites are from Dungeon Crawl.
Daily development on lanarts continues.

Windows binary snapshot: download

Recent changes:
  • Playing with getting the balance back in order. The recent stat overhaul has mixed things up a bit considerably. Before enemies use to scale as you got further with a simple formula, that has been taken out, in favour of static enemy difficulties. This has caused a lot of rethinking of content.
  • As a result, a weaker batch of enemies were added to the beginning, the animals (sheep, rat, hound).
  • Quite a bit of code-base refactoring. I hope to soon have AI controlled allies, and generally, monsters that fight each other. The code paths for the players and enemies was considerably merged.
  • Certain monsters now leave blood puddles.
  • Projectile weapons have been added. These include bows with two kinds of arrows, and stones.
  • Small user interface usability niceness:
    • Right click around the minimap to see previously explored areas. 
    • Right click items to drag them onto other inventory slots. Drag onto same slot to drop. 
    • Left click on action bar to select current action. 
    • Right click on weapon/projectile in action bar to de-equip it.
  • Random level generation mark-up format has been expanded. For a variety of things, an ability to specify a number of random variations was added. The levels are hopefully more varied now.
  • For now, permanent death was made not the default. You now spawn on the level right below.