Heroic Fisticuffs!

Here's a place where I talk about games, applications, websites, and other things that I make for fun. Mostly roguelikes. And robots. Since my domain is hard to spell you probably came here on purpose.

Precipice - Sketch to Final Game

One thing that made this ProcJam different from other game jams was the amount of planning I did beforehand. Here's a sketch of what Precipice looked like before the jam:





And here's what I ended up with:



Pretty good, I think! Most of the powers are in there. I think when I was designing the game I didn't realize quite how quickly the abyss would swallow up the entire level. Because of this, certain features like the ground giving way underneath you even while resting (!!!) had to change. The 'grapple' ability was just too dangerous to use for adjacent monsters, so I turned it into a Teleport/Swap ability that works from anywhere in the map. This makes for a kind of cool situation where you can get yourself into a really dangerous spot, and then swap with an enemy and watch them fall, while you escape to safety.

Thank you to everyone who has played it so far -- I've gotten a lot of positive feedback about the game! If you feel like dropping me a line with comments, bugs, suggestions, etc... please feel free!



Precipice!

I decided to take a break from the constant Dungeon Dual/RoboCaptain tug-of-war going on in my brain and create a roguelike game for ProcJam. ProcJam was organized by Michael Cook and featured a lot of really interesting talks last weekend.

The result is a procedurally-oriented roguelike game called Precipice.


The terrain is procedurally generated, and the game is infinite -- as long as you can stay alive.

It is heavily inspired by Hoplite, DataQueen, and the sidewalks outside of my house:


The gameplay is turn-based but can get frenzied as the ground starts to fall away underneath you. You have access to an array of powers to help you rescue artifacts from a ruined landscape.

You can play here on itch.io, or on my own page.



Fighting the urge to use fantasy tropes

I have been working on an update of my 7DRL from earlier this year, Dungeon Dual. Since it is a web game, I can tell it was decently popular -- certainly by my (very low) bar it is the most popular 7DRL I have put out there.

As a 7DRL it had some rough edges. But the core mechanic of the "asynchronous co-op" was there. Since then I have been polishing up the actual gameplay, UI, fixing bugs, making cooler animations, refactoring messy code, and all of that fun stuff.



The problem is in the back of my head I am also designing another game. My 7DRL from a few years ago, RoboCaptain, was also decently popular, and I have had various versions "in development" since then.

So, my plan was "hey I will just freshen up Dungeon Dual, and then put it out there" then take whatever improvements I had made to my CoffeeScript/rot.js engine and apply them to RoboCaptain.

The problem I am encountering is that designing a "classic" dungeon-diver is so addicting. The more I work on it, the less I am fixing bugs and the more I am adding entirely new mechanics to the game. Why? I think it is because fantasy tropes give us (as designers) so much to fall back on. There is so much "lore" built into all of our heads that is easy to leverage. Even something so simple as "would you like this dagger or this axe" is actually leveraging a collective intelligence/memory/lore that exists within your players.

This sort of lore is also influenced by the fact that so many great roguelikes are in fact classic "dungeons & monsters" sorts of games. You go too far down the path and you find yourself developing brogue, or ToME, or DCSS, or <pick your favorite game>.

I feel comfortable saying that a dungeon game would probably be more popular than an "indeterminate future" game with "robots and stuff". At least initially. People on r/roguelikes will say that a great roguelike will win out, regardless. But I'm not so sure. I know my own experiences... not having a common "future robots & stuff" lore to leverage always makes me skeptical to start out with. The first time I have to choose between "laser pistol" and "plasma pistol" my brain starts to melt down.

The few existing great non-fantasy roguelikes such as DoomRL get around this problem by using existing canons. Everyone knows that Imps throw fireballs and that Cacodemons are very tough for early-game players.

So my question is: do other developers feel similarly? Is designing a fantasy dungeon simply "too fun"? How do you get players to get over the initial lack of "lore" and stick it out through the first couple ten-or-twenty plays of your game? Are there other future/sci-fi games that do this well?

Here's some more images for the curious. Cheers!






Dungeon Dual Back up

Oops. I went on a Heartbleed-triggered password changing spree and inadvertently broke the django database connection that powers part of Dungeon Dual. It's back up now! More on 2.0 in the next post.

Dungeon Dual Deaths Analysis




Dungeon Dual keeps track of when and where you die, but also what monster killed you. I used the data to make a quick chart, and posted it on reddit, where it received lots of interesting discussion!

How to Play Dungeon Dual

My successful 2014 7-Day Roguelike entry is Dungeon Dual!

You can read some of the unexciting progress here on 7drl.org.

So, now that I've had a few hours to recover, here is

How to Play Dungeon Dual

Co-Op

You can play with a friend! This is accomplished through the wonderful magic of WebSockets. You don't need to play with a friend, but you may want to.

After you start a game, you can send the URL to your friend. It will also show up on the main page until someone joins.

Each game is totally asynchronous, which means you don't have to wait up for the other player. This is one thing I hate about other attempts at "multiplayer" roguelikes. In Dungeon Dual, the classic turn-based roguelike mechanic is unbroken.

However, if you have an ally certain tactics are available:

  • You will regain stamina faster
  • You can trade items
  • You can chat and share knowledge of items/levels (e.g. "hey don't drink this one, it's poison")
  • Certain abilities can be used cross-realms:
    • The Apprentice can shoot fireballs and cast entanglement onto the other screen, affecting your ally's enemies (hopefully you don't set your friend on fire in the process)
    • The Apprentice can banish monsters to the other screen
    • The Squire can use Warcry and Defend on the other screen to aid his ally
These features will only be possible when you and your ally are "in sync". This is determined by a fuzzy algorithm that takes into account both spacial distance (how far away are you on the grid of the same dungeon level) and time, which is determined by turn count. The game will try to help you out and give hints when you are out of sync such as "Hurry" or "Wait" or "Change levels".

From a technical perspective, the WebSockets server needs to be running for co-op to work. It runs on my webhost, but seems to die every now and then. If you notice it is offline, try to reach me via e-mail, over IRC on #rgrd, or via twitter. Server status is displayed on the main dungeondual.com page

Basic Mechanics

There are two stats in the game: Health and Stamina.

Stamina

Stamina is used to cast spells and abilities. It recharges over time, but not if you are in combat or are poisoned. Flasks of Vigor will instantly refresh your stamina and increase your maximum by 1. Stamina also scales damage of certain spells like fireball and forcebolt.

Stamina is also used up when you take damage in combat. Be sure not to take on too many enemies at once or you will not be able to recover!

Health

Lose all of your health and you are dead. Once your stamina is gone, any damage will go directly to your health. Health does not recover naturally, but can be restored using Flasks of Health. Flasks of Health will also increase your maximum health by 1. 

Abilities

Both classes have unique abilities. You can quickly toggle them using the 1-5 number keys. You can also access them from the 'z' menu. Abilities are activated by pointing and clicking with the mouse. Clicking on your ally's screen will use the ability on their screen instead.

Fireball
Apprentice ability. Engulfs a 3x3 grid of enemies (and potentially you) in a ball of fire. Damage scales with maximum stamina. Can be used with allies.
Entanglement
Apprentice ability. Stuns a 3x3 grid of enemies for a short period of time. Can be used with allies.
Forcebolt
Apprentice ability. Casts a powerful bolt of force at a single target, with damage scaling with maximum stamina.
Banish
Apprentice ability. Sends a single enemy across the realms for your ally to deal with. It might be a good idea to warn him or her in advance!
Charge
Squire ability. Enables you to dash across the screen and attack an enemy in a single turn, as long as they are at least 1 square away. This is great for hunting down pesky archers and spell-throwers.
Warcry
Squire ability. Unleashes a terrible roar that scares all enemies in sight, causing them to flee for a short period. Great for clearing a room or cornering a troublesome enemy. Can also be used with allies.
Defend
Squire ability. Temporarily boosts your (or your ally's) defense, protecting you against damage. Can also be used with allies.

Flasks

You will find a few (six? seven? I forget) types of potions (or "ye flask" as I call them) scattered across the dungeons. Some are bad, most are good. In standard roguelike fashion, you will not know what they are until you try them for the first time.
Vigor
Also known as flasks of stamina, these will refill your stamina and also boost your maximum by 1. You will almost always find a flask of vigor or health on each level.
Health
These flasks are critical to survival in the dungeons. They will refill your health and also boost your maximum by 1. You will almost always find a flask of vigor or health on each level.
Fire
Drinking from this flask will cause you to burst into flames. Generally speaking this is a bad thing.
Weakness
The contents of this flask will drain you of your stamina. Try not to taste it during combat.
Might
Doubles your melee damage for a few turns. Time the use of these wisely.
Invisibility
Temporarily makes you invisible to all enemies. Great for getting out of a sticky situation.

Other Equipment

You will find armor and weapons in the dungeon as well.
Armor
At this point in the game, armor doesn't do much other than subtract potential damage from monster attacks.
Weapons
You will find an increasingly deadly array of weapons in the dungeon. Some have special effects such as piercing (attacks an enemy behind your target), smashing (attacks all surrounding enemies), and stunning (stuns enemies 1/3rd of the time).
Hats
They don't do anything I just wanted to have a game with hats!!
PS If you've read this far, e-mail me or make a comment and I will add your own personal hat to the game. ;)


Thanks for playing. Always happy to hear feedback, good and bad. And congratulations to all other 2014 7DRL participants.

Roguelike Animations using Javascript and ROT.js

I am using javascript (CoffeeScript, actually) and the excellent ROT.js library to build the next version of RoboCaptain. ROT.js is great; it makes easy stuff really easy and hard stuff much more straightforward. It allows you to break away from messing around finely tuning your Field Of View algorithm or your AStar pathfinding routines and spend time building actual game content.

I am something of a roguelike traditionalist (you can find me calling out non-roguelikes in a hopefully non-obnoxious manner on reddit) but while I do love my ASCII, I also think looking nice is important. Once I started building ranged weapons into my game I realized: I need animation.

In most programming languages, animation is straightforward. Start an animation, wait, continue where you left off. In javascript, this is less straight forward, because javascript is a single-threaded non-blocking language. The appropriate way to do delays in javascript is to use setTimeout. However, if your code looks like this:

player.shoot(monster, weapon)
game.drawAnimation(player, weapon)
game.damage(monster)
if monster.is_dead() then
     game.kill(monster)

Then your game will not work like you think it will. As your drawAnimation function starts firing off setTimeout events and drawing (for example) the path of the shot across the screen with 20ms delays, the javascript machine continues chugging along. This means while your player is watching their bullets fly across the screen, javascript has already moved on and is now calling your damage() and potentially kill() functions. Even worse, depending on how you write your code, it will keep going and eventually start moving other monsters, potentially even the one that just got shot.

Despite the cries of anguish from javascript purists, my first idea was to implement a delay() function, forcing javascript to wait until my animation drawing was done before continuing. Javascript purists hate this because you will be blocking the entire browser (or tab?) while this is happening. I figured when programming a game, this was OK, because it's not like anything else is happening on the page other than the game.

Unfortunately if you use this method ROT.js will not ever get a chance to run it's display handling code, and your canvas will not update until after your terrible delays are over.

Using setTimeout is a pain in the ass. It makes coding very messy because you have to use callbacks, something like this:

player.shoot(monster, weapon)
game.drawAnimation(player, weapon, function () {
    game.damage(monster)
    if monster.is_dead() then
        game.kill(monster)
          }

Using this method I was still running into asynchronous event issues, and gods help you if you want to nest your animations (like a shooting rocket causing an explosion).

After a lot of messing about, I came up with a relatively elegant solution. It might be obvious to some, but no amount of googling on my part led me to anything similar.

You have to treat your animations as game actors. They need to have turns just like the player and monsters. They use the same drawing routines, screen updating routines, etc.

Now my game loop looks something like this (in CoffeeScript, but you should get the idea):

endPlayerTurn: () ->
    @nextTurn()

nextTurn: () ->
    if @hasAnimations()
        first_animation = @animations[0]
        @animationTurn(first_animation)
        return

    next_actor = @scheduler.next()

    if next_actor.group == "player"
        @finishEndPlayerTurn()
        return

    if next_actor.objtype == "monster"
        monster = next_actor
        @intel.doMonsterTurn(monster)
        @finishEndPlayerTurn()
        @nextTurn()
        return

animationTurn: (animation) ->
    animation.runTurn(@, @ui, @my_level)
    if not animation.active
        @removeAnimation(animation)

    @finishEndPlayerTurn()
    
    setTimeout(=>
        @nextTurn()
    50)
    return

So, now my code is much more readable. Players and monsters take turns as per usual, using the ROT.js scheduler. Animations are independent actors that always take their turns first, if any are still alive. Within each animation there is a runTurn() method that calls the relevant game code: draw a rocket, draw a flash of light, move an expanding circle outwards, etc. The setTimeout is there but feels much less intrusive to me this way. 

The code exits out to the main ROT.js loop elegantly, and does not hold up any operation or freeze the browser. The last step is to just make sure the player can't move or start a new turn until the animation is over (this last part is somewhat optional, in my experience roguelikes are not that fast-paced anyways).

The tricky thing is thinking of animations as game actors. The player may launch a rocket or a laser, but it will be the actual laser actor that calls the attack() or damage() functions. It is a little tricky to get used to, but I find it much more simple and easy to code than worrying about callbacks.

Your mileage may vary, but I wanted to put this out there so the next developer trying to solve this problem can save a few days of refactoring hell