Sunday, January 31, 2010

Magecrawl changes license, now new BSD license'ed

Magecrawl is being relicensed under the new BSD license (3-clause). As the sole copyright holder of the source code, (the other person who helped at the very beginning both assigned me copyright and is cool with the change), I'm able to do this with little fuss.

The reason I did this is primarily due to my interested in MEF, which seems pretty cool and I'm interested in trying. MEF is licensed MS-PL, which is basically BSD licensed with a clause that says roughly "if we have software pattens that apply you can use them, but if you sue us, we can sue you back using them." I actually don't mind that clause in general, unfortunately the GPL isn't compatible with it since it is an additional restriction.

More generally however, the idea that the source code I wrote couldn't be combined with something arguably under fewer restrictions annoyed me. I realized that I had defaulted to GPLv3 not out of any ideological reason, but just because it seemed like a good place to start. I think the BSD license is closer to what I want, since I'm not expecting any commercial software company to eat up my code and sell it without giving me any credit. If you do happen to grab a segment of code larger than a "small chunk", letting me know and a note in the credits would be nice however. :)

Note: Technically I realized that tech demo I and II have been released without the required license text files saying libtcod, libtcod-net are both BSD and sdl is lgpl. If someone really wants to me, I'll pull them all down off the website and make fresh ones with the text files attached. I've fixed hg truck, so new released will be correct.

Plans for Iteration 8 and beyond

Now that iteration 7 and the second tech demo is out the door, it's time to sketch out some plans. The plan is two more tech demos to flesh out more functionality, and then I'll start with "slices". A "slice" would involve fleshing out content for say one class and some themed levels. Each slice would be a balanced mini-game within itself. Once I get a few of those slices done, I could release what I'd consider a "real" game.

With that, iteration 8 is planned to take a month and a half to two months. The headlining feature, taken from "How to Write a Roguelike in 15 steps", is "Experience". This'd involve different level monsters, a skill tree for the player, and increasing difficulty. Beyond that, I have a significant number of refactoring and rearchitecture tasks to handle.

The high level goal for iteration 9 would be hacking out the basic parts of the economy. My thoughts on that can be found here, here, and here.

Non-English operating systems, InvariantCulture, and You

As previously mentioned, Magecrawl's 2nd tech demo was released yesterday. I received multiple reports of crashes during map generation. After some detective work, it became clear that all of the reporters were using non-English versions of Windows (Polish and French). This issue can be boiled down to this simple string:


In some languages, the period there is replaced with a comma. All of the .NET languages by default, defaults to using the system language. This creates problems when text written using one separator is read by a program using another. The text is read fine, but when you try to parse it to a number, an exception is thrown.

The solution easy (arguably hacky) solution that somebody showed me is to tell .NET to "stop doing that".

            CultureInfo previousCulture = Thread.CurrentThread.CurrentCulture;
            Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture;
            //Your code here that depends on reading/writing text between languages.
            Thread.CurrentThread.CurrentCulture = previousCulture;

What we're doing here is saving off the current thread's "culture" and setting it to InvariantCulture. Then we can do our operation, and reset it back when we're done. In my case, I wrap the functions save/load functions and those that read spell/item/monster information.

If you've been bitten by this issue in Magecrawl, grab the new release here. If you use an English OS, there isn't a behavior change.

Saturday, January 30, 2010

Magecrawl Tech Demo II is released!

After a late night of testing and tweaking (and one more feature),  I think I'm ready to call tech demo II done and get it out the door!

For those that are keeping score at home, the new feature is the idea of "camp supplies". Think "tent" from Final Fantasy if you've played that game. When you use it, you burn n number of turns resting, restoring a nth amount of your hp/mp every turn. If a monster comes into view, you wake up and loose the rest of the effect. I'm not sure if that's how I want to handle things in the full game, but it works great for the tech demo.

To respond to jice's question, yes at this point I'd consider it a game. It's replayable, and somewhat fun. I have a long way until i consider it finished however, hence the fact that it's a "tech demo".

Grab it while it's hot. It's been testing on Windows 7 and Ubuntu 9.10. You'll need the .NET 3.5 framework if you're on windows (, or some form of mono on Linux (apt-get install mono-runtime for Ubuntu).

When you download it, you might want to edit Preferences.xml to change your name and hit '?' in game to learn the controls.

Friday, January 29, 2010

Iteration 7 is Feature Complete!

Tonight I just finished up iteration 7's last feature. Some of the new features of this release include:

  • Different monster types with varying behaviors
  • Armor (and the basic bits of a real combat system)
  • Betters graphics (more color, HP bars, etc)
  • Move to location/run in direction
  • Lots of fixes to ranged weapons and spells
  • Lots of speed/memory optimizations
I'm pretty happy with how this iteration has gone. Now that I'm feature complete, I want to do some tweaking before I kick out a tech demo II. Right now, all the monsters have similar stats, and game is too difficult to be considered finishable.  Once I get that done, I'll post again with the details.

Sunday, January 24, 2010

Some things take much longer to finish than you expect, others not so much...

Predicting how long a task will take is apparently both difficult and something one I am not very good at. The reason this comes up stems from the fact that I'm using the "Student and Startup" version of FogBugz for my bug tracking needs. Besides being epically awesome in general, it also has a feature where you can set estimates on how long tasks will take. After telling it when your working on something and when you finish, it can figure out how accurate your estimates are and give you a good estimate on when you will get done with everything targeted in your current milestone.

Eitherway, FogBugz told me there was almost no chance for me to get everything done that I wanted in a week, assuming that I spend as much time as I currently am programming. I used this to punt on some "nice to have" things, and get to work on what I really wanted to have for next week. One of things was to finally finish all my issues with ranged attacks.

I finally had sorted out all the bugs and issues with lightning bolts yesterday, only to find out that I had uncovered a bunch of flaws with the other forms of ranged attack (via weapons or single target spells). Both would show the animation if you targeted an empty square, but hit nothing (and not use ammo/MP). There were three different code paths for showing ranged animations, and three different ways we were calculating targets and damaging them. Sorting all these out took a good 4 or 5 hours, but now things are more sane.

One I got that submitted, the next task was to have enemies remember where a player was if they duck out of sight. That way, they could head that way and "chase" the player. That took all of 20 minutes to implement, test, and submit. It really is a toss up sometimes, I was expecting that to take longer.

The estimate is 12 hours of work left before I can start testing the tech demo II. Time to get back to work.

Wednesday, January 20, 2010

Easy version control - Why you should submit early and often

So the changelist that brought writing this post to mind was this one. I was working last weekend on Magecrawl, and decided that writing a "lightning bolt" like effect shouldn't be that hard. It just travels in a straight line until it hits a wall (or travels a maximum distance), hitting every creature in its way. In developing it, I violated a rule I try to follow in development both at home and work.
  • Each change should only do one things (Add feature, fix bug, fix performance issues)
  • Corollary: Submit each change independently as early as possible
It started off with the basic infrastructure for a new effect type, and then it ballooned into multiple different attempts of writing new subroutines to calculate the path of projectiles. After that, I didn't like the way it looked when drawn, so I refactored how we drew all ranged attacks on the screen. I also tacked on some unrelated preference settings, and some bug fixes.

The reason this was a problem was that things took twice as long as they would have if I was disciplined. Keeping changes small makes testing them before submission easier. It makes reviewing before submission easier (I review my changes for submission to keep myself from submitting lazy hacks). After each part, the lightning bolts still weren't right, and I got discouraged from hacking on them further.

Submitting early and often forces you to keep biting off small bits of the problem, fixing or implementing just that part, testing just that small part, and getting it submitted so you can work on another. I find that it works well for me.

    libtcod-net 1.5.0b2 release

    (Your regularly scheduled roguelike posts will continue after this brief announcement.)

    Magecrawl uses libtcod-net heavily both for graphics display and in the game engine (pathfinding, LOS, etc). libtcod-net is just a thin c# wrapper layer around libtcod that I wrote before starting Magecrawl.. I think for any new roguelike, written in either in a managed or unmanaged language, libtcod is a wonderful library to use. jice, the writer and maintainer, is very responsive with suggested features and bug fixes.

    A new version of the library has come out in beta, 1.5.0b2. It adds random name generation, sub-cell image blitting, and a few other things I can't think of right now. I'm not currently in need of any of the new features, but it seemed like a good time to update the wrapper and move Magecrawl to use it.

    If your in the market for a library to sit your new or current roguelike under take a look at libtcod and/or libtcod-net.

    Sunday, January 10, 2010

    The Joys of Emergent Behavior - Monsters that work "well" togeather

    So today I had a moment where my own AI monsters did something I thought was "clever". I have two monster AIs hacked out so far, the Sprinter and the Ranged. The Sprinter can "rush" when within two moves of the player. This moves them next to the player and immediately attacks, but then takes an an extra turn before it gets to go again. The ranged can use a sling as a ranged attack, requiring a turn to reload between shots. The only "intelligent" AI is the Ranged. If it figures out there are others monsters nearby the AI, it tries to move away if it's inside the minimum range of their weapon (unless they are at melee, then they swap to melee and attack).

    So, I ran into a Ranged in the hallway and chased it down. The hallway opened up into a room, with another Ranged and a Sprinter. The Ranged I has caught up to backed off due to nearby help. I chased it again, and the sprinter rushed me, one Ranged hit me and the other moved back again. As I dealt with the sprinter I took another two sling stones to the face, and then turned on one of the Ranged. As I chased it, the other backed away and kept pelting me as the other ran away. In the end I ran out of health and died.

    Now, from the code's point of view nothing special happened. Each actor followed it's simple rules. However, from my point of view the buggers seemed smart for a second. The Sprinter tanked me, while the Ranged moved away and nuked me. I was kited by one of the enemies since I didn't have ranged attack while the other killed me. That sounds like something more out of a WoW boss fight than Magecrawl.

    It was an amazing to see simple rules you write turn into something that seems "alive" even just for a moment.

    Saturday, January 9, 2010

    Monster classes and types - Making the opposition interesting

    As I mentioned here, my plan is that every 2 months or so to have a "tech demo" release until I have enough of the game to release something fun and playable. While I have a lot of other nice things in this release, a list of which I'll cover later, one of the major bullet points for this release is better and more varied monsters. The last release just had a single type of identical monsters wander around. Current trunk has something that looks more like:

    It's still in work, but the idea I'm most excited about in this area of the game is the differences between "Classes" and "Types". A class of monster is kinda like a base class, it defines behavior and types of abilities it has access to. A type of monster is a specific instance of a type with a given name and stats. Both Orcs and Brigands would be standard monsters, even if their equipment and stats are different.

    Here's my current list of ideas (With a typical example):
    • Standard (Orc Warrior) - Average "intelligent" monster, able to use basic ranged attack if it has one as it closes the distance. Might try to escape when in trouble (unsure on that).
    • Healer (Human Healer) - Tries to stick near elite, standard, or ranged monsters (at range of player). Weak on its own, but can buff and heal other monsters.
    • Ranged (Goblin Slinger)- Ranged "blaster", either with traditional weapons or magic.
    • Sprinter (Wolf) - Fast monster that will quickly enter melee range (possibly with rush attack).
    • Swarmer (Giant Ants) - Individually weak monsters that try to stick together and attack en mass.
    • Elite (Dragon) - The big guns of the dungeon. A challenge in themselves. Ignores all other monsters (due to pride?) and charges towards player.
    The issue in general is to make the game interestingly difficult without being impossible. I've modified the map generate to be intelligent in how it places monsters, so I could drop "groups" of monsters near each other.  After I write out the basic AI for each type, the trick will be balancing. Any thoughts? Obvious (in terms of other roguelikes) classes of monster that I've forgotten?

    Wednesday, January 6, 2010

    Economy in Roguelikes - Part 3: Ideas and Plan

    After part one of this series where I laid out my thoughts on economy systems in RPGs and part two where I set out some goals any system should try to accomplish, here is where I'm to lay out my profound revolutionary system (sarcasm), or at least what I'm planning to use in Magecrawl. A bit intimidating of an order to say the least.

    Quintessence, a silvery weightless goo of compressed arcane power, is my current idea for the primary currency. The destruction of non-magical items wrought be sentient beings would provide a small amount, with larger amounts coming from harvesting magical items. This allows us to somewhat easily convert items that the player finds that aren't useful into something that is, albeit at a low exchange.

    To prevent the hoover problem (needing to pickup every single item for optimal play), I'm planning that items that aren't "masterwork" or would be wrongly sized for the player to be automatically converted to Q. This gives a great fluff reason why many monsters would drop "gold", unlike in many games when you kill a wolf and it drops gold.

    Due to the properties of Q, we could have skills that allow the mage to convert it into magical items, recharge wands, or create enchantments. This also could allows us to have "shops" in the dungeon, either areas that have the correct equipment or some other form. This makes more "fluff" sense then a shopkeeper just chilling in the dungeon of doom.

    Since it's the mage who is creating and using Q, unlocking new equipment and such can be based upon skills. You can take "Enchant Equipment III" when you hit level 12 at the earliest, as a made up example, or you could delay to take some other awesome skill. There could be skills to increase efficiency in creating or using Q, allowing some builds to be more artificer (awesome equipment, average skills) while others more fireball down the door and blow everything up (who needs equipment beyond what I can find).

    Since in essence this system is points based, balancing could be done by changing Q costs and skill level requirements. It allows on average people to get the equipment they need, without reducing the awesomeness of finding just the equipment item you need in a chest. I think it also brings a unique "feel" and "fluff" to the game.

    So, that's the idea. I'm at least 2 iterations away from implementing even a basic version of some of the concepts. However, fleshing it all out now gives me something to think about and plan around. Thoughts? Questions?

    Monday, January 4, 2010

    Not forcing a square peg into a round hole: Stairs

    So this story begins with a somewhat unexpectedly, I was working on map generator optimization (here) and rarely I'd hit an exception I wasn't expecting. The exception said I tried to generate a given level 10 times and failed. Digging into it further, the stitch map generator was having issues placing the required number of "chunks", coming up short the number I figure made decent map sizes.

    It only did this when I turned off the cave map generator, and generated more than 50 levels in a row. In the end, it was a very interesting bug, so I hacked together a few "paint" pictures to explain what happened.

    The stitch map generator has a "canvas map", sized 250x250, that "chunks" are placed upon. Stairways were initially very simple, a mapobject that only kept track of if it was "up" or "down". The map generator placed an "up" on one level at the same x,y position as the "down" on the level above.

    Here, we generator a map in the box, and place the stairs down at the dot.

    Here, on the next level, we try to generate a map centered on that "down" stairs. We hit the edges of the "canvas", and have to generate a smaller map. We again randomly place the stair going down in the lower right.

    Here, we try again to generate a map centered on the "down" stairs. It's at this point that we can't generate a valid map and give up.

    While in this artificial example it took 3 iterations, in my bug it took 20+. Also, when the "cave" map generator was used 50% of the time for a level, we'd have a large number of spots in the "center" for the downstairs, which could reset the progression.

    The correct solution for my issue was to decouple the stairways from requiring the same x,y position between two levels. The naive solution was to add a few properties to the Stairs to keep track of both floors positions. This was very problematic. MapObjects, which stairs is a subclass along with doors, cosmetic items, and treasure chests, are expected to be located only on one map, in one position. I could add logic to figure out what level we're on when we ask for the position, and return that. However, what about levels being generated, and didn't have a known floor yet?

    After about a half an hour of hammering a square peg into a round hole, I changed my approach. MapObjects were expected in one position, so keep them that way. I gave each stairway a unique id (guid), and created a class that stores where that stairway leads. I had to serialize this information for save and load, but beyond that it was straightforward.

    Now, stair's positions are decoupled between levels. I ran a few level generations of 1000 levels, and haven't run into the original bug case. This also simplified both my map generators. On average, it also speed up the cave map generator, since we don't have to restart when we don't have clear floor where we require a stairway.