Wednesday, December 28, 2011

From collaborator hell to dependency-free bliss

Let's say you're a developer on a C# app and just got a batch of new requirements. Let's also say that it has a fairly simple requirement: when a user closes his or her account, the account status should be set to closed. You, as a good Test Driven Developer, write your unit test, watch it fail, then add the following code to the Account class and watch your test pass:

public void Close() {
  this.Status = AccountStatus.Closed;
}

Easy-peasy.

Many weeks later you come across a requirement that says when a user closes his or her account, the account owner's IsSpecial flag should be set. To make your latest unit test pass, you do the simplest thing that could possibly work.

public void Close() {
  this.Status = Status.Closed;
  this.Owner.IsSpecial = true;
}

Your test went from Red to Green and now it's time to Refactor. Having your Account mutate some value on another object breaks encapsulation so you Control+R & E to Extract Method then Control+R & M to Move your method to the Owner class and give it a clear name.

public void Close() {
  this.Status = Status.Closed;
  this.Owner.ClosedAccount(this);
}

That's a little better. In stead of just setting some property you're now telling the other object that something happened. It's free to handle that how it wants and the Account needn't know about it - a textbook case of encapsulation and The Hollywood Principle.

You've worked in this domain long enough to know that all kinds of things are going to happen when an account is closed. Maybe the other things should be moved to a different method? Not entirely YAGNI, but modifying the account and telling others about it are two different things so you could make the argument that they should be two separate methods.

public void Close() {
  this.Status = Status.Closed;
  this.OnClosed();
}

private void OnClosed(){
  this.Owner.ClosedAccount(this);
}

It's a judgment call but this does clearly separate things. There's one method where the account modifies itself and another where all the other objects get to react to the account being updated. You know that if you have requirements that start with when an account is closed, then the code that deals with the account should go in the Close method and code that deals with other classes goes in the OnClosed method. But wait a second - doesn't this sound familiar? There's even something built into the C# language for just this scenario: events and delegates.

public delegate void ClosedEventHandler(object sender, AccountClosedEventArgs e);

public class Account {
  public event ClosedEventHandler Closed;

  public void Close() {
    this.Status = Status.Closed;

    if (this.Closed != null)
      this.Closed(this, new AccountClosedEventArgs(this));
  }
}

Now the owner can register a callback with the account's Closed event and the account can just tell whoever's listening when it has closed. The Account class no longer needs to reference the Owner class and we can rejoice.

But something about this still doesn't quite seem right. Now instead of the Account telling the Owner, the Owner has to listen to the Account. You still have one class directly referencing and depending on another - you just switched the dependency direction. A day or two later you remember you once read something about Messaging and Domain Events so you look it up and get that started.

public void Close() {
  this.Status = Status.Closed;
  EventBus.Publish(new AccountClosed(this));
}


/* ... elsewhere ... */
public class AccountClosed{
  public Account Account { get; set; }

  public AccountClosed(Account account){
    this.account = account;
  }
}


/* ... elsewhere ... */
class AccountEventsHandler {

  private IRepository repository;

  public AccountEventsHandler(IRepository repository){
    this.repository = repository;
  }

  public void Handle(AccountClosed e){
    this.repository.GetOwner(e.Account).IsSpecial = true;
  }
}


/* ... elsewhere ... */
public void WireEverythingTogether(IRepository repository){
  var accountHandler = new AccountEventsHandler(repository);
  EventBus.Subscribe<AccountClosed>(accountHandler.Handle);
}

Much nicer. Not only is the dependency completely removed from the Account and Owner classes, but each class no longer exposes methods that are just used for reacting to each other. The Account only modifies itself and creates events and the Owner only modifies itself and creates events. Now you have the Account depend on the EventBus and somewhere else you subscribe other classes to the appropriate events. You have a simple AccountEventsHandler that deals with coordinating other objects when the account changes so the domain objects themselves don't need to. It can be little bit more work, but it does reduce coupling and allow much cleaner domain objects. But the Account is still referencing the EventBus directly. You could use a service locator:

public void Close() {
  this.Status = Status.Closed;
  Services.Get<IEventBus>().Publish(new AccountClosed(this));
}

But the service locator can cause problems - like if an IEventBus wasn't registered - and is considered an anti-pattern by some people. You could use constructor injection to pass in the dependency instead.

public Account(IEventBus bus){
  this.eventBus = bus;
}

public void Close() {
  this.Status = Status.Closed;
  this.eventBus.Publish(new AccountClosed(this));
}

That's clean and a good use of the Dependency Inversion Principle, but maybe overkill since the EventBus is so simple and ubiquitous. Maybe hard coding a such a simple dependency is ok. In my limited experience this is an acceptable time for bending the rules and relying on a statefull static class. You will probably never hear me say that again since I hate working with static classes and singletons that have state or complex behavior. On the other hand, constructor injection is a great first step to using a Dependency Injection Container to wire up the EventBus and other dependencies.

Maybe months from now you can switch to actual full-scale event sourcing and CQRS. With command handlers, the Domain Objects create events and store them locally. Then the command handler would fetch all the events from the domain objects and pass them to the event bus; your domain objects don't even need to know about the event bus anymore since the command handler deals with that.



This series of slightly different ways of dealing with different objects involved in the same action is something that I've had a lot of problems with. It's all the same basic result but the important difference is in how to deal with coordinating different objects when something happens. These different designs have very different benefits and costs. The earlier examples are easy for anyone to do, easy to see exactly what happens when debugging or looking at the code, and you know exactly where to make the change: right in the method itself. The downside is that it becomes collaborator hell and all your classes quickly become so bloated that most of the class's code is actually about other classes. The classes become so interdependent that you can't change one thing without having to change everything else. Having lots of dependencies like this can also make build times suffer too. The latter examples only rely on the Event Bus, Service Locator, or Inversion Of Control Container so they are as non-dependent as possible but I can't really tell what's going to happen when the program runs. If everything is wired together with configuration in xml files or sql tables then it can also become exceedingly tedious and error prone to change. This kind of configuration becomes especially difficult if the developer's environment, qa environments, and production environments can all be configured differently. Not everyone will be familiar with or even like the event-based designs either. The whole thing is made even more confusing if different sections of the project have different ways of doing things and people mostly just do whatever they copied and pasted from - not that I've ever done that of course....

Sunday, December 4, 2011

Randomized metroidvania update: simple fix

I decided to take a break from skeletal animation and focus more on the room generation for the Metroidvania game. While implementing architecture and art for underground caves, I think I found out how to fix the problem where transitioning into a new room would "bump" you to an unexpected place. I say that I "found out" instead of "figured out" because it was really just a lucky observation and a lot of trial and error.

Why did it take me so long to figure that out? Well, the problem seemed to only happen when transitioning from one room to another room, while on the floor (i.e. not jumping), where the new room's ceiling was lower on the world map than the room you were transitioning from. Confusing enough? Even then, it only happened some of the time. After a lot of logging, I noticed an odd pattern: the player's new y coordinate would be slightly higher than expected (by less than a single pixel). After all that, here's the fix:

private function checkBounds():void
{
    /* code to figure out the new room and new player.x and player.y values */

    if (toRoom.mapY > currentRoom.mapY)
        player.y -= 1; // gravity causes weird things sometimes....   
}

Although I'm glad to finally have this fixed, as far as I can tell at least, I'm still annoyed by it. What annoys me most is that I have no idea how I could have prevented this and no idea how to even figure out this fix. It was a lot of observation and trial and error. There were at least a dozen previous attempted fixes that didn't fix anything. I still don't fully understand why having a y coordinate of 144 is ok but one of 144.00115 causes you to fall to the room below and have a different x coordinate. I don't even know how to write a unit test for this: it involves Flixel physics, Flixel collision detection, and the constantly changing algorithms that I use to create rooms. Could TDD have prevented this? Is there a unit test that reliably shows what the bug was?

Tuesday, November 22, 2011

Skeletal animation with actual images

It took quite a while because I was busy with other things and because I tried to do too much at once. Once I broke the problem down into smaller pieces, things quickly fell into place. I had to rewrite most of the underlying bones and joints but I eventually got the skeletal animation to work with actual graphics. Here's a half-sized Galamoth from C:SOTN.




I completely got rid of bones and just use FlxSprites now. Each FlxSkeletonJoint is responsible for how the parent and child are positioned relative to each other. Joints do this by attaching the "ball" point of one sprite to the "socket" point of another at a given angle. Each joint can still have it's own set of animations. The FlxSkeleton itself just holds the bones and joints and helps assemble and detatch them.

Next is basic inverse kinematics - just enough to support things like "look at that" or "put your right hand here". That should be good enough for what I need.

Monday, November 14, 2011

Skeletal animation with basic physics

Skeletons are now FLxObjects and not FlxGroups. There's a few other minor changes but that's the big one. You can see the bounding box by toggling debug mode by pressing [~] then clicking the bounding box icon in the upper right.

Press [w] to jump, [a] or [d] to walk, hold [shift] to run, and [k] to kill yourself. There's not really anything to do here - just jump around and self-kill when bored.




Completed this time:
  • Make sure skeletons play well with Flixel physics (collisions, velocity, acceleration, etc.)
  • Detaching bones (exploding skeletons!)
  • More animations for HumanoidSkeleton
Next up:
  • Make sure regular sprites and animated sprites can be used as bones
  • Constraints for joints
  • Ragdoll physics

Friday, November 11, 2011

Skeletal animation with forward kinematics

I'm off to a good start with my little detour into skeletal animation: I've got bones and joints that can be animated. The joints use keyframes to hold the target angle of the joint and interpolate between the current angle and the target angle so switching animations should smoothly transition from one to the other. I had a bit of difficulty since rotating a FlxSprite rotates about the center and I need to rotate around the joint. I also wasted nearly an hour on a really stupid bug where animating a joint would sometimes make it freak out (hint: -45 as a Number and -45 as a uint are two very different values). Once I'm done, I plan on submitting a pull request to the original Flixel project so maybe the next version of Flixel will support skeletal animations....




Done so far:
  • FlxSkeletonBone - a FlxSprite with a start and end point
  • FlxSkeletonJoint - a class that connects bones at an angle
  • FlxSkeleton - a FlxGroup that wires everything together
  • HumanoidSkeleton - a pre-made human skeleton with some generic animations
  • Keyframe based animations per joint
  • Simple forward kinematics

To do:
  • Make sure regular sprites and animated sprites can be used as bones
  • Make sure skeletons play well with Flixel physics (collisions, velocity, acceleration, etc.)
  • Constraints for joints
  • Ragdoll physics
  • Detaching bones (exploding skeletons!)
  • Add inverse kinematics
  • More animations and helpers for HumanoidSkeleton
  • Walking velocity determined by feet positions?

Monday, November 7, 2011

Randomized metroidvania 09: slightly better architecture

It took a few days longer than I'd like but I was able to implement some new stuff:
    Slopes and stairs based off of https://github.com/krix/SlopesTest/blob/master/src/FlxTilemapExt.as
    Different background colors, patterns, and decorations (just columns for now)
    Different art and architecture preferences for each region




Each region gets a few architecture and art preferences. The architecture preferences are used to determine the room generation: what tiles are empty, solid, or stairs. The art preferences are used to determine the graphics: what do the background, stairs, walls, and floors look like. There's still not much content but I have a simple system set up and I can add more and better art or architecture styles later. Alpha content will suffice for now.

There's still a bug where moving into another room sometimes bumps you to a different space. I can't pinpoint where or why though. It seems to only bump you towards the bottom of the map and never the top. It also seems to bump to the left and never the right. So it bumps up the Y axis and down the X axis.... weird.

I think it's time to focus on player graphics for a while. Since I'm better at programming than art, I think I'll try my hand at skeletal animation. Flixel doesn't have anything like this as far as I can tell but forward and inverse kinematics should be fun and make a lot of the art stuff easier too. I estimate about a week until I have something useful.

Friday, November 4, 2011

Randomized metroidvania 08: initial architecture

I've got some initial architecture. It should now be possible to move from any room to any room. It certainly ain't pretty and is sometimes frustrating to get the jumps but it does allow you to get from one room to the next. I also added restricted regions to the world generation so the resulting map won't be as dense.


[it seems that transitioning from one room to the other sometimes "bumps" the player to a different spot... worked fine on my machine. I'll look a little closer into that code.]

Part of automating the process of creating room internals was changing the screen size to 32x21 tiles. Next up is adding sloped tiles for stairs, more variety, and more randomness to the architecture.

Wednesday, November 2, 2011

Randomized metroidvania 07: a brief study of C:SOTN and vertical movement

Next up is adding stairs, lifts, and all the architecture that should go in a room. It's already possible to move to rooms to the left and right, but moving up the map isn't possible yet. I decided to turn to maps of Castevania: Symphony Of The Night to see how they did it.

First of all, Alucard seems to be two tiles wide and three tall. When he jumps his feet are 4 tiles higher than they were when he started. This means that the shortest ceiling is 3 tiles from the floor and he can jump onto a tile that has a top that's 4 tiles off the floor.


Sometimes a flight of stairs or a hole in the floor is good enough to go up. The second image has three tiles from the top of the stairs to the bottom of the next floor - just short enough to jump up.
 


Sometimes you have to make a series of jumps between sides and center blocks.
 

Jumping back and forth between side platforms is the most common. These pictures show that sometimes it's more obvious than other times.
 



Here's a short series of jump-through blocks.

 This could be either a series of jump-through blocks or jumping from side to side depending on how you look at it. This pattern shows up in a few places.

There are also a few places with moving platforms to bring you up or down.
  
And this is my favorite example: a weird mix of side to side, stairs, and just jumping up through a hole in the floor. I don't remember if it's here or another place but I think you can't get to some of the non-essential side platforms until you have double-jump. The background graphics and variety make it much more interesting. 
A simple algorithm for this might be: evenly place floors, add a flight of stairs going up each third floor,  remove the floor from the top of the stairs to the opposite wall, remove the floor form the base of the stairs to the nearest wall, and add a gap in each remaining floor. The background shadows, floors, stairs, and banisters at the top of the staircases are good examples of the attention to details found in C:SOTN.



So it seems that sometimes you just walk up some stairs or do a single jump, at other times you have to jump from side to side, or sometimes from sides to center blocks, or even have to jump through one-way blocks. The best places (in my opinion) are a crazy mix of each. This is probably much easier to automate if your screen can be split into an even number of floors like the last image; that way you could place the floors and then pick random ways to move between them. It's also good to point out that each region has it's preferred method for going up. Don't forget to carefully plan the architecture to match the height and jump hight of the main character.

This may have been pretty obvious to most of you, but I learned a lot: you can use the same action (jump to the upper left then the upper right then the upper left....) but provide slight differences in art and layout and it will still provide interesting architecture and opportunities for fun. I've also got some working code to create rooms with layouts like the last image. And lastly; after 15 years, C:SOTN is still a beautiful and intricate game.

Tuesday, November 1, 2011

Randomized metroidvania 06: player in a room

I slept through my alarm this morning and woke up 4 hours after I should have been at work so I had a lot of time for this project today.

Time for the first steps to actual gameplay: a rectangle that can walk and jump around empty rooms. Press [m] to view the map.




Nothing exciting yet - just rooms with connections to the neighboring room. There's not even a way to go up stairs or anything so you eventually end up at a dead end. It was tricky getting the player to walk out one door and appear in the correct one in the correct room; it would have been easier to just create one huge map with the entire level but that would take a lot of memory and the camera would look into rooms you haven't been into yet. Next I'll add background graphics along with stairs and floors within each room; later the room sizes and shapes can be determined by the region. I'll also make sure the map only shows rooms you've been into and a few more tweaks. The really fun stuff will begin once I can generate some decent architecture. I should think of an overall theme I'd like to work with too.

Monday, October 31, 2011

Randomized metroidvania 05: map specials

I was going to create the player and rooms to move around in but I decided to add a little more to the map. The very crowded map now shows bonus items in dead ends and secret rooms. You can also mouseover a room to see details about it's region, name, and contents.




There's a limited amount of keys and locks so if you add enough regions then you'll see "key #33" or something like that.

Here are some things I added:
    for each unlocked dead end, there's a chance of adding a random lock and bonus
    when placing a room, there's a chance of adding a secret room
    when placing a room, there's a chance of a name and or some item

I'm not sure what theme or items I want in the final game, the ones here are just examples. Once I get the mechanics figured out (like how to create an interesting map) then I can figure out what theme I want to have.

I think I've crammed all I can into the map for now; time to start on actual gameplay.

Sunday, October 30, 2011

Randomized metroidvania bugs

My last post had a bug where new regions may not connect to a neighboring room. I thought about fixing it before posting but decided to post it anyway. Now I hate buggy software as much as anyone, and this bug makes the map unsolveable since there can be rooms you can't get to, so why did I post it anyway?

  1. This is a work in progress and sometimes - most of the time - those are messy. Sometimes it's good to see how things are made, bugs and all.
  2. I already fixed it when I changed that part of the code for my next iteration.
So what caused the bug in the first place? I changed the code that connects a room when it is placed. I made it keep placing connections until the new room is connected to another room in the same region unless it was the first room in that region. Otherwise a room could be placed and it would connect to a neighboring room from a different region and not connect to a room in it's own region. If that was the room that had the key and it was locked with that corresponding lock, then there was no way to get the key. In fixing that bug I introduced a new one.

Randomized metroidvania 04: map keys and locks

It took a few hours and I had to rewrite the code for doors, but I've got keys and locks working. A major part of any metroidvania is exploring new areas and finding some kind of impassable barrier -  a wide pit, a wall of metal blocks, a force field, etc. It's only later when you find some new ability or item - double jump, rocket launcher, field disabler, etc - that you can go past the barrier to a new region. You can abstract this idea of obstacles and items as "locks" and matching "keys". In one game the "red locks" and "red key" might represent actual locked doors and a key, in another game they might be security cameras and a disguise, and in another game they might be tall walls and the ability to climb walls.

You can see the locks and keys in my latest update. The icons are ugly and hard to distinguish, but it's just for example and will be redone many times before the project is over. Consider this the "alpha version" artwork.



[I just noticed that sometimes the first room in a region doesn't connect to the other rooms. A bug I just fixed when I redid that code for my latest work.]

when visiting a region for locks and keys:
    place an unused key
    put the matching lock on one door to each unvisited adjacent region
    any unlocked doors to adjacent regions are added to a "to do" list
    visit each unvisited adjacent region

after visiting all regions for locks and keys:
    for each unlocked door on the "to do" list, put a random used lock

This ensures that each region has a key that you need to proceed to the neighboring regions. Although; since each region can connect with its neighboring regions multiple times, it's theoretically possible that getting the first key would be enough to explore all regions.

Saturday, October 29, 2011

Randomized metroidvania 03: map regions

Different regions! It was easier than I thought it would be - of course it has been in the back of my head for a while so I didn't jump in with my first thought.




to start a new region:
    create a list of all frontiers for all rooms
    randomly pick one to start the new region
    clear all other frontiers
    place a room as normal

The current region determines the room color and a randomly chosen maximum height and width and can easily be used to control more things like the artwork, items, bad guys, secrets, and other things. Like placing doors, this algorithm is guaranteed to connect with existing regions and could even connect multiple other regions.

Randomized metroidvania 02: map connections

The rooms are now connected! I used a very simple method that's guaranteed to connect and will even create loops to join different "branches" that happen to be near by.




to connect a room:
    after placing a room
    get a list of all adjacent cells that are in a room
    randomly pick some cells to create doors

This can create multiple opening between two rooms. That's not a problem yet but it could be tweaked later on. This should be enough to create the gameplay portion but I'd like to work on defining different regions at the world-gen level before I move on to creating a player to run around.

Friday, October 28, 2011

Randomized metroidvania 01: map rooms

So I've decided to make a randomized metroidvania game; sort of a cross between a roguelike and one of the greatest games ever, Castlevania: Symphony Of The Night. Since I want this to be a flash game, I'll use Eclipse and Flixel.

I spent a few hours getting Eclipse to work with Actionscript and playing with Flixel a bit. For my game I decided to start with generating a random world map.




Here's the basic algorithm, which took less time than setting up Eclipse and Actionscript:

to start:
    add a 1x1 "frontier" space
    add a room

to add a room:
    randomly pick a frontier space
    start a room there
    randomly grow it for a while
    remove frontiers under it
    add its empty neighboring cells to the list of frontiers

roguelike tutorial retrospective

Time for a retrospective! I think retrospectives are probably the most important thing a developer could do since reflecting on what you've done is the difference between someone with 10 years of experience and someone with 1 year of experience repeated 10 times. There are as many different styles of retrospectives as there are teams who do them but I've found that these few questions are usually good enough for me.

What does this retrospective cover? For this roguelike tutorial I tried a few new and different things; the most obvious one being that this was a series of blog posts. This is also the first time I had something of a magic system. There were also a few tweaks to how I normally code things. The roguelike itself is not meant to be a complete and fun game - it's just a basic example of a few different things that may help some roguelike developers - so I'm not going to review the game as a game.

What worked well? As far as code goes, the Screen interface worked really well. Having goblins pickup and use items is neat, as is learning the identity of things by watching others use them. As far as a tutorial goes, I think it can be useful for some people. Even though like there are, at most, a handful of people who read each post, I did get more feedback than I expected, including a reference to behavior trees - something I didn't know about. I'm also pleased that my posts were a mix of code, references to other sources, and some of my own thoughts about why I'm doing what I'm doing. It's not as authoritative as saying "THIS IS THE ONE TRUE AND RIGHT WAY TO DO THIS", and some of it was probably a bad idea, but I've always found that the most instructive guides show the uncertainties and mistakes we run into and how to deal with them.

What needs to work better? One thing I've learned about building something via blog posts is that refactoring is very difficult to show; it's easy to show new code to add, especially if you have small classes and methods, but I don't know of any easy way to show removing, rewriting, and refactoring. This means that the final code, although mostly small pieces, could use a lot of cleanup work and this isn't representative of my best code and may even be a bad example. Oops. Using no globals was a laudable goal but lead to some things I'm not to happy about, like the path finder using a collection of points rather than a fixed size 2D array since I didn't have an easy way to tell the path finder what the map size is. There are a few minor annoyances with the code, like the creature class becoming a behemoth, but most of that is from adding features without refactoring. The spell-related code is quite ugly and clunky too - mostly because I'm not familiar with writing magic systems and couldn't refractor so it was just charging ahead with little idea what I was doing.

What should be done differently next time? I still think a series of blog posts can make a good tutorial but it needs something that makes it easier to show refactoring - perhaps putting everything on github and discussing the changes each time. That would be a slight improvement but still not quite good enough. I tried to be very structured to make sure I followed through and didn't lose interest halfway through but it would have been better to break it into more than 20 parts. I've used Ninject to make it easier to wire things together and something like that would work well for this kind of project; I thought about it but didn't want to add another post about how to use a dependency injection framework.

In summary
  • Use something to counter the limits to show refactoring on a series of blog posts.
  • Don't limit yourself to a fixed number of posts.
  • The Screen interface was the best thing I did: keep doing that.
  • The AsciiPanel was useful but it needs work to avoid flickering and to support animations.
  • Each post should have code, narration, references, and details about the author's thought process.
  • I liked getting comments from readers even more than I thought I would.
Any feedback from you, readers? What did you like? What did you not like? What could be done differently?

Tuesday, October 25, 2011

roguelike tutorial 20: item appearance and identification

One of the things I enjoy the most about roguelikes is identifying items. Usually not by scroll of identify or funky unicorn tricks, but by boldly quaffing and reading — yeah, I tend to die a lot. Identification is something I haven't seen in other games and something that I think any good roguelike needs. The basic idea is that if we've identified an item, or given it a name of our own, then we see the name, otherwise we see it's appearance.

So let's start by adding an appearance to the Item class.

private String appearance;
public String appearance() { 
    if (appearance == null)
        return name;

    return appearance;
}

Then have it passed in to the constructor.

Since the appearance has to be passed in, our code is broken in all the places that create items - this is a good thing because now we can see what areas we need to change. If appearance is null then the name is used since most things look like what they are; e.g. a rock looks like a rock and a sword looks like a sword. Because of this we can pass a null appearance to all items except the potions. Before we get to the potions we need to set up some colors and text to use in our factory.

private Map<String, Color> potionColors;
    private List<String> potionAppearances;
 
    public StuffFactory(World world){
        this.world = world;
  
        setUpPotionAppearances();
    }
 
    private void setUpPotionAppearances(){
        potionColors = new HashMap<String, Color>();
        potionColors.put("red potion", AsciiPanel.brightRed);
        potionColors.put("yellow potion", AsciiPanel.brightYellow);
        potionColors.put("green potion", AsciiPanel.brightGreen);
        potionColors.put("cyan potion", AsciiPanel.brightCyan);
        potionColors.put("blue potion", AsciiPanel.brightBlue);
        potionColors.put("magenta potion", AsciiPanel.brightMagenta);
        potionColors.put("dark potion", AsciiPanel.brightBlack);
        potionColors.put("grey potion", AsciiPanel.white);
        potionColors.put("light potion", AsciiPanel.brightWhite);

        potionAppearances = new ArrayList<String>(potionColors.keySet());
        Collections.shuffle(potionAppearances);
    }

This creates some text ("red potion", "green potion") and some corresponding colors (red, green), and shuffles the text so each time you create a new factory they will be in a different order. Since we create a new factory once per game, each game will have different text and colors.

Now to set the potion color, just use the new data when creating potions.
String appearance = potionAppearances.get(0);
  Item item = new Item('!', potionColors.get(appearance), "health potion", appearance);
...and...
String appearance = potionAppearances.get(1);
  Item item = new Item('!', potionColors.get(appearance), "mana potion", appearance);
...etc.

Not the best way but it works well enough and is easy to show.

If you play the game the potions should be random colors now.



Our CreatureAi class needs to record what items have been identified and what the names of identified or renamed items are. We could have this tracked by the items themselves or some global variables, but this way creatures can identify and converse about item appearances too. By modeling it as close to reality as possible, i.e. where creature's have their own mind and names for items, interesting possibilities and emergent behavior are more likely.

private Map<String, String> itemNames;

    public String getName(Item item){
        String name = itemNames.get(item.name());
        return name == null ? item.appearance() : name;
    }
 
    public void setName(Item item, String name){
        itemNames.put(item.name(), name);
    }

And we need this to be available to the creatures.

public String nameOf(Item item){
    return ai.getName(item);
}
 
public void learnName(Item item){
    notify("The " + item.appearance() + " is a " + item.name() + "!");
    ai.setName(item, item.name());
}

Wherever we use an item's name we need to use nameOf instead. The easiest way I can think of doing this is to make the name method private and see where the code breaks. Those places need to use the creature's nameOf method instead. The only places we need the real name is in the PlayScreen where we check to see if the player has the Teddy Bear, or whatever the victory object is, and in the CreatureAi getName and setName methods. Once we make all the other changes we can make the name method public and everything should compile again.

Run it and you should see that potions are now listed by color instead of the real name. I got an error when starting but that was fixed by setting up the GoblinAi before the Goblin is given a weapon and armor.


Now we need to let the player identify things when used. In the StuffFactory, for each quaffEffect make the creature learn the name of the potion if any effect happens. Here's an example:

public Item newPotionOfHealth(int depth){
    String appearance = potionAppearances.get(0);
    final Item item = new Item('!', potionColors.get(appearance), "health potion", appearance);
    item.setQuaffEffect(new Effect(1){
        public void start(Creature creature){
           if (creature.hp() == creature.maxHp())
               return;
    
           creature.modifyHp(15);
           creature.doAction("look healthier");
           creature.learnName(item);
       }
    });
  
    world.addAtEmptyLocation(item, depth);
    return item;
}

The Creature's throwAttack should also let the thrower learn the name of anything that has a quaffEffect. Now when you play you can learn the identity of potions by quaffing or throwing them.



It's still possible to learn the name of an Effect that you shouldn't learn. If you kill a bat with a yellow potion you shouldn't be able to see the effect it had or identify what the potion is since the bat died from being hit by a bottle and there wasn't anything left for the effect to apply to. Let's create a version of the doAction method that handles this logic. This version of doAction will take a message and an item. Anyone who can see the creature should see the message and learn the identity of the item, unless the creature is dead, then nothing happens. We can reuse some of the code in the current doAction.

public void doAction(String message, Object ... params){
    for (Creature other : getCreaturesWhoSeeMe()){
        if (other == this){
            other.notify("You " + message + ".", params);
        } else {
            other.notify(String.format("The %s %s.", name, makeSecondPerson(message)), params);
        }
    }
}
 
public void doAction(Item item, String message, Object ... params){
    if (hp < 1)
        return;
  
    for (Creature other : getCreaturesWhoSeeMe()){
        if (other == this){
            other.notify("You " + message + ".", params);
        } else {
            other.notify(String.format("The %s %s.", name, makeSecondPerson(message)), params);
        }
        other.learnName(item);
    }
}
 
private List<Creature> getCreaturesWhoSeeMe(){
    List<Creature> others = new ArrayList<Creature>();
    int r = 9;
    for (int ox = -r; ox < r+1; ox++){
        for (int oy = -r; oy < r+1; oy++){
            if (ox*ox + oy*oy > r*r)
                continue;
    
            Creature other = world.creature(x+ox, y+oy, z);
    
            if (other == null)
                continue;
    
            others.add(other);
        }
    }
    return others;
}

A possibility:
The goblin quaffs a cyan potion.
The goblin looks stronger.
The cyan potion is a strength potion!

If the potions' quaffEffect use this new doAction then things will work a little better and we can remove the learnName from throwAttack since everyone who see's the creature will identify the potion based on the effect. This also means that if your creatures quaff potions everyone who watches can figure out what it is.


And that's one way of doing item identification. We had to make a lot of little changes all over the place, like using the creature's nameOf instead of the item's name method, but it wasn't difficult or error-prone. Each creature has it's own idea of what things are named so you could even let intelligent creatures tell each other the names. Maybe each goblin should quaff one unidentified potion during it's life and then discuss with others.

public void discussItemName(Item item, Creature other){
    creature.doAction(item, "say \"%ss are %ss\"", item.appearance(), item.name());
    other.learnName(item);
}

Or all goblins could share the same itemNames map — although that's kind of cheating.

You could also let the player give names to things. A RenameItemScreen that let's you rename a specific item or all items with a specific appearance. That way if the player deduces that red potions are healing potions then they can rename all red potions to healing potions or if they like their individual sword they can rename it Excalibur.


One last addition: add a "cause of death" string to the creature class and pass one in to modifyHp.

private String causeOfDeath;
 public String causeOfDeath() { return causeOfDeath; }
 
 public void modifyHp(int amount, String causeOfDeath) { 
     hp += amount;
     this.causeOfDeath = causeOfDeath;
  
     if (hp > maxHp) {
         hp = maxHp;
     } else if (hp < 1) {
         doAction("die");
         leaveCorpse();
         world.remove(this);
     }
 }
Then update the places that modify hp. Here's an example:
public void modifyFood(int amount) { 
    food += amount;
  
    if (food > maxFood) {
        maxFood = (maxFood + food) / 2;
        food = maxFood;
        notify("You can't belive your stomach can hold that much!");
        modifyHp(-1, "Killed by overeating.");
    } else if (food < 1 && isPlayer()) {
        modifyHp(-1000, "Starved to death.");
    }
 }
Then pass the player to the WinScreen and LoseScreen and you can tell the user details about how they died, what they were carrying, conduct, etc. download the code

Friday, October 21, 2011

roguelike tutorial 19: mana, spells, and magic books

Time to add some magic to our game. This will take a lot of code and will take a long time to balance. To keep it simple, we'll just use mana and spellbooks. Scrolls are easy to add since they're basically potions that you read instead of quaff so I won't add scrolls - but I'm sure you can figure out how to do that by now or at least you will be able to after this tutorial.



We're going to have spells and each will have it's own effect. But when we add that effect to a creature, we want each creature to get it's own effect, otherwise weird things will happen because the spell will have an effect being applied to many creatures and the shared state (like duration) will be wonky. Instead, a spell will have an effect and when applying it to something we can create a copy and apply the copy. That way each time you cast a spell the effect will have it's own state. Add a copy constructor to the Effect class like this:

public Effect(Effect other){
    this.duration = other.duration; 
}


The first new class we'll need is a Spell class to tie together a spell name, cost, and effect.

package rltut;

public class Spell {

    private String name;
    public String name() { return name; }

    private int manaCost;
    public int manaCost() { return manaCost; }

    private Effect effect;
    public Effect effect() { return new Effect(effect); }

    public Spell(String name, int manaCost, Effect effect){
        this.name = name;
        this.manaCost = manaCost;
        this.effect = effect;
    }
}
See how we return a copy of the effect instead of the original?


Let's add the mana related stuff to the Creature class.

private int maxMana;
    public int maxMana() { return maxMana; }
    
    private int mana;
    public int mana() { return mana; }
    public void modifyMana(int amount) { mana = Math.max(0, Math.min(mana+amount, maxMana)); 
    
    private int regenManaCooldown;
    private int regenManaPer1000;
    public void modifyRegenManaPer1000(int amount) { regenManaPer1000 += amount; }

    private void regenerateMana(){
        regenManaCooldown -= regenManaPer1000;
        if (regenManaCooldown < 0){
            if (mana < maxMana) {
                modifyMana(1);
                modifyFood(-1);
            }
            regenManaCooldown += 1000;
        }
    }

Don't forget to initialize regenManaPer1000 in the constructor and to call regenerateMana during the update method. We also have something else we can gain during a level-up.
public void gainMaxMana() {
        maxMana += 5;
        mana += 5;
        doAction("look more magical");
    }

    public void gainRegenMana(){
        regenManaPer1000 += 5;
        doAction("look a little less tired");
    }


And add the new options to the LevelUpController.
new LevelUpOption("Increased mana"){
            public void invoke(Creature creature) { creature.gainMaxMana(); }
        },new LevelUpOption("Increased mana regeneration"){
            public void invoke(Creature creature) { creature.gainRegenMana(); }
        }


It would also be nice to add our new stats to the displayOutput method of the PlayScreen class.
String stats = String.format(" %3d/%3d hp  %d/%d mana  %8s", 
    player.hp(), player.maxHp(), player.mana(), player.maxMana(), hunger());  


Adding spell books could be done many different ways. I think we should stick with the Item class and extend it with what we need.
private List<Spell> writtenSpells;
    public List<Spell> writtenSpells() { return writtenSpells; }

    public void addWrittenSpell(String name, int manaCost, Effect effect){
        writtenSpells.add(new Spell(name, manaCost, effect));
    }

Don't forget to initialize writtenSpells in the Item constructor. This should allow us to create scrolls, spell books, notes, or even engraved items. Very simple and flexible. If we create an effect that just displays a note to the user then we could even let the player add his own non-magical engravings to items.

Let's add some spell books. I'm going to create two simple ones but you should add more spells and more books. These are just examples of what can be done. The first is a healer type book:
public Item newWhiteMagesSpellbook(int depth) {
        Item item = new Item('+', AsciiPanel.brightWhite, "white mage's spellbook");
        item.addWrittenSpell("minor heal", 4, new Effect(1){
            public void start(Creature creature){
                if (creature.hp() == creature.maxHp())
                    return;
                
                creature.modifyHp(20);
                creature.doAction("look healthier");
            }
        });
        
        item.addWrittenSpell("major heal", 8, new Effect(1){
            public void start(Creature creature){
                if (creature.hp() == creature.maxHp())
                    return;
                
                creature.modifyHp(50);
                creature.doAction("look healthier");
            }
        });
        
        item.addWrittenSpell("slow heal", 12, new Effect(50){
            public void update(Creature creature){
                super.update(creature);
                creature.modifyHp(2);
            }
        });

        item.addWrittenSpell("inner strength", 16, new Effect(50){
            public void start(Creature creature){
                creature.modifyAttackValue(2);
                creature.modifyDefenseValue(2);
                creature.modifyVisionRadius(1);
                creature.modifyRegenHpPer1000(10);
                creature.modifyRegenManaPer1000(-10);
                creature.doAction("seem to glow with inner strength");
            }
            public void update(Creature creature){
                super.update(creature);
                if (Math.random() < 0.25)
                    creature.modifyHp(1);
            }
            public void end(Creature creature){
                creature.modifyAttackValue(-2);
                creature.modifyDefenseValue(-2);
                creature.modifyVisionRadius(-1);
                creature.modifyRegenHpPer1000(-10);
                creature.modifyRegenManaPer1000(10);
            }
        });
        
        world.addAtEmptyLocation(item, depth);
        return item;
    }

And the second has a hodgepodge of spells that do weird things, mostly to show what can be done within the effects:
public Item newBlueMagesSpellbook(int depth) {
        Item item = new Item('+', AsciiPanel.brightBlue, "blue mage's spellbook");

        item.addWrittenSpell("blood to mana", 1, new Effect(1){
            public void start(Creature creature){
                int amount = Math.min(creature.hp() - 1, creature.maxMana() - creature.mana());
                creature.modifyHp(-amount);
                creature.modifyMana(amount);
            }
        });
        
        item.addWrittenSpell("blink", 6, new Effect(1){
            public void start(Creature creature){
                creature.doAction("fade out");
                
                int mx = 0;
                int my = 0;
                
                do
                {
                    mx = (int)(Math.random() * 11) - 5;
                    my = (int)(Math.random() * 11) - 5;
                }
                while (!creature.canEnter(creature.x+mx, creature.y+my, creature.z)
                        && creature.canSee(creature.x+mx, creature.y+my, creature.z));
                
                creature.moveBy(mx, my, 0);
                
                creature.doAction("fade in");
            }
        });
        
        item.addWrittenSpell("summon bats", 11, new Effect(1){
            public void start(Creature creature){
                for (int ox = -1; ox < 2; ox++){
                    for (int oy = -1; oy < 2; oy++){
                        int nx = creature.x + ox;
                        int ny = creature.y + oy;
                        if (ox == 0 && oy == 0 
                                || creature.creature(nx, ny, creature.z) != null)
                            continue;
                        
                        Creature bat = newBat(0);
                        
                        if (!bat.canEnter(nx, ny, creature.z)){
                            world.remove(bat);
                            continue;
                        }
                        
                        bat.x = nx;
                        bat.y = ny;
                        bat.z = creature.z;
                        
                        creature.summon(bat);
                    }
                }
            }
        });
        
        item.addWrittenSpell("detect creatures", 16, new Effect(75){
            public void start(Creature creature){
                creature.doAction("look far off into the distance");
                creature.modifyDetectCreatures(1);
            }
            public void end(Creature creature){
                creature.modifyDetectCreatures(-1);
            }
        });
        world.addAtEmptyLocation(item, depth);
        return item;
    }

I had to add a couple methods to the creature class to support these.
public void summon(Creature other) {
        world.add(other);
    }
    
    private int detectCreatures;
    public void modifyDetectCreatures(int amount) { detectCreatures += amount; }
    
    public void castSpell(Spell spell, int x2, int y2) {
        Creature other = creature(x2, y2, z);
        
        if (spell.manaCost() > mana){
            doAction("point and mumble but nothing happens");
            return;
        } else if (other == null) {
            doAction("point and mumble at nothing");
            return;
        }
        
        other.addEffect(spell.effect());
        modifyMana(-spell.manaCost());
    }

And update one method:
public boolean canSee(int wx, int wy, int wz){
        return (detectCreatures > 0 && world.creature(wx, wy, wz) != null
                || ai.canSee(wx, wy, wz));
    }

You should also add a potion to restore mana.


Now we just need a ReadScreen to select an item to read, a ReadSpell screen to select a spell from the item, and a CastSpell screen to select a target for the spell. You should be able to figure those out but let's do them in the opposite order:
package rltut.screens;

import rltut.Creature;
import rltut.Spell;

public class CastSpellScreen extends TargetBasedScreen {
    private Spell spell;
    
    public CastSpellScreen(Creature player, String caption, int sx, int sy, Spell spell) {
        super(player, caption, sx, sy);
        this.spell = spell;
    }
    
    public void selectWorldCoordinate(int x, int y, int screenX, int screenY){
        player.castSpell(spell, x, y);
    }
}


The ReadSpellScreen is very similar to the InventoryBasedScreen.
package rltut.screens;

import java.awt.event.KeyEvent;
import java.util.ArrayList;

import rltut.Creature;
import rltut.Item;
import rltut.Spell;

import asciiPanel.AsciiPanel;

public class ReadSpellScreen implements Screen {

    protected Creature player;
    private String letters;
    private Item item;
    private int sx;
    private int sy;
    
    public ReadSpellScreen(Creature player, int sx, int sy, Item item){
        this.player = player;
        this.letters = "abcdefghijklmnopqrstuvwxyz";
        this.item = item;
        this.sx = sx;
        this.sy = sy;
    }
    
    public void displayOutput(AsciiPanel terminal) {
        ArrayList<String> lines = getList();
        
        int y = 23 - lines.size();
        int x = 4;

        if (lines.size() > 0)
            terminal.clear(' ', x, y, 20, lines.size());
        
        for (String line : lines){
            terminal.write(line, x, y++);
        }
        
        terminal.clear(' ', 0, 23, 80, 1);
        terminal.write("What would you like to read?", 2, 23);
        
        terminal.repaint();
    }
    
    private ArrayList<String> getList() {
        ArrayList<String> lines = new ArrayList<String>();
        
        for (int i = 0; i < item.writtenSpells().size(); i++){
            Spell spell = item.writtenSpells().get(i);
            
            String line = letters.charAt(i) + " - " + spell.name() + " (" + spell.manaCost() + " mana)";
            
            lines.add(line);
        }
        return lines;
    }

    public Screen respondToUserInput(KeyEvent key) {
        char c = key.getKeyChar();

        Item[] items = player.inventory().getItems();
        
        if (letters.indexOf(c) > -1 
                && items.length > letters.indexOf(c)
                && items[letters.indexOf(c)] != null) {
            return use(item.writtenSpells().get(letters.indexOf(c)));
        } else if (key.getKeyCode() == KeyEvent.VK_ESCAPE) {
            return null;
        } else {
            return this;
        }
    }

    protected Screen use(Spell spell){
        return new CastSpellScreen(player, "", sx, sy, spell);
    }
}


And the ReadScreen is simple enough.

package rltut.screens;

import rltut.Creature;
import rltut.Item;

public class ReadScreen extends InventoryBasedScreen {

    private int sx;
    private int sy;
    
    public ReadScreen(Creature player, int sx, int sy) {
        super(player);
        this.sx = sx;
        this.sy = sy;
    }

    protected String getVerb() {
        return "read";
    }

    protected boolean isAcceptable(Item item) {
        return !item.writtenSpells().isEmpty();
    }

    protected Screen use(Item item) {
        return new ReadSpellScreen(player, sx, sy, item);
    }
}


Update the PlayScreen to add spell books, map the r key to the new ReadScreen, and update the HelpScreen.



I know this is already a very long tutorial, but there's one little thing that bugs me. The creature class has some special methods that are only called when gaining a level, the gain* methods. They're only called from one place and they're small enough that we can just inline them. Eclipse, and most IDEs, can do this for you, just select the method name, right click, Refactor, Inline.



(days later....) I just realized a different, and almost certainly better, way of handling effects for each spell. Instead of each spell having a reference to an Effect and using the Effect's copy constructor, the spell class should act as an Effect factory and have an abstract newEffect method. Each individual spell would subclass Spell and implement newEffect. I'll have to do that with my next roguelike.

download the code

Tuesday, October 18, 2011

roguelike tutorial 18: potions and effects

Let's add some potions now. When the player, or smart monster, quaffs a potion it's effect will be applied to the creature. What can we say about effects? Most effects will only last for a certain duration. Some will apply every turn or every few turns (like poison or a slow heal). Others will have a change that lasts for the duration (like confuse or resist cold). This can be done with a start method that that applies the change when first quaffed, an end method that unapplies the change when the duration has run out, and an update method that is called every turn in between. I think this will cover the basics so let's get started on them. We'll create a base Effect class and subclass it with specific effects.

package rltut;

public class Effect {
        protected int duration;
        
        public boolean isDone() { return duration < 1; }
        
        public Effect(int duration){
                this.duration = duration;
        }
        
        public void update(Creature creature){
                duration--;
        }
        
        public void start(Creature creature){
                
        }
        
        public void end(Creature creature){
                
        }
}

And add a quaffEffect to Item class.

private Effect quaffEffect;
public Effect quaffEffect() { return quaffEffect; }
public void setQuaffEffect(Effect effect) { this.quaffEffect = effect; }

Now that we've got items that can have effects when quaffed it's time to add some potions to our StuffFactory. I'll start with three simple ones that show three ways of doing things.

Here's a simple one-time potion where the work happens in the start method:
public Item newPotionOfHealth(int depth){
    Item item = new Item('!', AsciiPanel.white, "health potion");
    item.setQuaffEffect(new Effect(1){
        public void start(Creature creature){
            if (creature.hp() == creature.maxHp())
                return;
                                
            creature.modifyHp(15);
            creature.doAction("look healthier");
        }
    });
                
    world.addAtEmptyLocation(item, depth);
    return item;
}

Here is a potion that affects the creature each turn.
public Item newPotionOfPoison(int depth){
    Item item = new Item('!', AsciiPanel.white, "poison potion");
    item.setQuaffEffect(new Effect(20){
        public void start(Creature creature){
            creature.doAction("look sick");
        }
                        
        public void update(Creature creature){
            super.update(creature);
            creature.modifyHp(-1);
        }
    });
                
    world.addAtEmptyLocation(item, depth);
    return item;
}

Here's one that will affect the creature at the start and restore it at the end.
public Item newPotionOfWarrior(int depth){
    Item item = new Item('!', AsciiPanel.white, "warrior's potion");
    item.setQuaffEffect(new Effect(20){
        public void start(Creature creature){
            creature.modifyAttackValue(5);
            creature.modifyDefenseValue(5);
            creature.doAction("look stronger");
        }
        public void end(Creature creature){
            creature.modifyAttackValue(-5);
            creature.modifyDefenseValue(-5);
            creature.doAction("look less strong");
        }
    });
                
    world.addAtEmptyLocation(item, depth);
    return item;
}

And add a randomizer to help us add the new potions.

public Item randomPotion(int depth){
                switch ((int)(Math.random() * 3)){
                case 0: return newPotionOfHealth(depth);
                case 1: return newPotionOfPoison(depth);
                default: return newPotionOfWarrior(depth);
                }
        }

Now let's go back to the creature class and make sure it's calling the right methods at the right times. It will need a list of effects that are currently applied to it.

private List<Effect> effects;
public ListList<Effect> effects(){ return effects; }

Don't forget to initialize it in the creature's constructor.

Add quaff method to Creature class. Since they're so similar, the quaff and eat methods can share some code.
public void quaff(Item item){
            doAction("quaff a " + item.name());
            consume(item);
        }

        public void eat(Item item){
            doAction("eat a " + item.name());
            consume(item);
        }
        
        private void consume(Item item){
            if (item.foodValue() < 0)
                notify("Gross!");
                
            addEffect(item.quaffEffect());
                
            modifyFood(item.foodValue());
            getRidOf(item);
        }
        
        private void addEffect(Effect effect){
            if (effect == null)
                return;
                
            effect.start(this);
            effects.add(effect);
        }

Create a new method to update the effects each turn and remove any that are done. Don't forget to call the end method before removing the effect.

private void updateEffects(){
            List<Effect> done = new ArrayList<Effect>();
                
            for (Effect effect : effects){
                effect.update(this);
                if (effect.isDone()) {
                    effect.end(this);
                    done.add(effect);
                }
            }
                
            effects.removeAll(done);
        }

Call this during the creature's update method. The player can't quaff anything until we create a QuaffScreen to go with our new behavior.

package rltut.screens;

import rltut.Creature;
import rltut.Item;

public class QuaffScreen extends InventoryBasedScreen {

        public QuaffScreen(Creature player) {
                super(player);
        }

        protected String getVerb() {
                return "quaff";
        }

        protected boolean isAcceptable(Item item) {
                return item.quaffEffect() != null;
        }

        protected Screen use(Item item) {
                player.quaff(item);
                return null;
        }
}

I think I've said this before but that InventoryBasedScreen is really paying off. Now bind that to the 'q' key in the PlayScreen class and update the createItems method, also in the PlayScreen class, to add some potions. Try 3 or 4 per level to start with. Don't forget to update the HelpScreen too. Play around and change the durations or strengths or abundance of potions. Try adding some new ones that change the vision radius, heal over time, stop regeneration, consume extra food, or fill someone's stomach. I'm sure you can think of even more potions and effects. There you go; potions and effects that are simple and flexible. We're going to add a few more effects in the next tutorial when we add magic.



Wouldn't it be cool if throwing a potion at a creature caused the effect to apply to it? Easy-peasy.

private void throwAttack(Item item, Creature other) {
    commonAttack(other, attackValue / 2 + item.thrownAttackValue(), "throw a %s at the %s for %d damage", item.name(), other.name);
    other.addEffect(item.quaffEffect());
}

Then make sure the throw method removes the item if it has a quaffEffect and the target was a creature, otherwise it should add to the world like it already does. Now you can sit back and chuck poison bottles at goblins.

download the code

Friday, October 14, 2011

roguelike tutorial 17: smarter monsters

Now that we've got all these nifty items and abilities, wouldn't it be cool if our cave monsters could use them too? Let's create a new creature capable of using weapons.

We can start with a GoblinAi that's almost exactly the same as the ZombieAi.

package rltut;

import java.util.List;

public class GoblinAi extends CreatureAi {
    private Creature player;

    public GoblinAi(Creature creature, Creature player) {
        super(creature);
        this.player = player;
    }

    public void onUpdate(){
        if (creature.canSee(player.x, player.y, player.z))
            hunt(player);
        else
            wander();
    }

    public void hunt(Creature target){
        List<Point> points = new Path(creature, target.x, target.y).points();
    
        int mx = points.get(0).x - creature.x;
        int my = points.get(0).y - creature.y;
    
        creature.moveBy(mx, my, 0);
    }
}


Now let's add newGoblin to the StuffFactory. Goblins start with random weapons and armor.
public Creature newGoblin(int depth, Creature player){
        Creature goblin = new Creature(world, 'g', AsciiPanel.brightGreen, "goblin", 66, 15, 5);
        goblin.equip(randomWeapon(depth));
        goblin.equip(randomArmor(depth));
        world.addAtEmptyLocation(goblin, depth);
        new GoblinAi(goblin, player);
        return goblin;
    }

Now update creature's equip method to make sure anything equipped is added to the inventory if it isn't already there.
public void equip(Item item){
        if (!inventory.contains(item)) {
            if (inventory.isFull()) {
                notify("Can't equip %s since you're holding too much stuff.", item.name());
                return;
            } else {
                world.remove(item);
                inventory.add(item);
            }
        }
    
        if (item.attackValue() == 0 && item.rangedAttackValue() == 0 && item.defenseValue() == 0)
            return;
    
        if (item.attackValue() + item.rangedAttackValue() >= item.defenseValue()){
            unequip(weapon);
            doAction("wield a " + item.name());
            weapon = item;
        } else {
            unequip(armor);
            doAction("put on a " + item.name());
            armor = item;
        }
    }

And add the missing method to the World class. This allows us to remove an object even if we don't know where it is.
public void remove(Item item) {
        for (int x = 0; x < width; x++){
            for (int y = 0; y < height; y++){
                for (int z = 0; z < depth; z++){
                if (items[x][y][z] == item) {
                    items[x][y][z] = null;
                    return;
                }
            }
        }
    }
}

We also need to make sure that when a creature dies it drops anything it was holding.
private void leaveCorpse(){
        Item corpse = new Item('%', color, name + " corpse");
        corpse.modifyFoodValue(maxHp);
        world.addAtEmptySpace(corpse, x, y, z);
        for (Item item : inventory.getItems()){
            if (item != null)
                drop(item);
            }
    }


After adding goblins to the addCreatures method of the PlayScreen you should be able to play and fight some rather tough goblins with armor and weapons of their own. Don't forget to tweak each creatures hp, attack, defense, the food values of corpses, and the xp gained or how much xp is needed for each level. You should also change the number of items and creatures per level. I prefer few items on the ground. That way the player almost has to confront goblins to get better loot.


So our goblins can use melee weapons and armor — that's nice — but they will run over and beat you with a bow instead of fire them. How about if they could throw things or fire from a distance? Time to work on the GoblinAi. My goblins will, in order of priority, try to: ranged attack, throw attack, melee attack, pickup stuff, and wander if they can't do anything else. Let's get to it.
public void onUpdate(){
        if (canRangedWeaponAttack(player))
            creature.rangedWeaponAttack(player);
        else if (canThrowAt(player))
            creature.throwItem(getWeaponToThrow(), player.x, player.y, player.z);
        else if (creature.canSee(player.x, player.y, player.z))
            hunt(player);
        else if (canPickup())
            creature.pickup();
        else
            wander();
    }
You should make your goblins do things in whatever order you want; maybe you want them to pick things up first so you could slow them down by putting some rocks or something between you.

The new helper methods are:
private boolean canRangedWeaponAttack(Creature other){
        return creature.weapon() != null
                && creature.weapon().rangedAttackValue() > 0
                && creature.canSee(other.x, other.y, other.z);
    }

    private boolean canThrowAt(Creature other) {
        return creature.canSee(other.x, other.y, other.z)
          && getWeaponToThrow() != null;
    }

    private Item getWeaponToThrow() {
        Item toThrow = null;
    
        for (Item item : creature.inventory().getItems()){
        if (item == null || creature.weapon() == item || creature.armor() == item)
            continue;
        
        if (toThrow == null || item.thrownAttackValue() > toThrow.attackValue())
            toThrow = item;
        }
    
        return toThrow;
    }

    private boolean canPickup() {
        return creature.item(creature.x, creature.y, creature.z) != null
          && !creature.inventory().isFull();
    }

It's a good idea to take these helper methods, and the hunt method, and move them up to the CreatureAi class. You can use the Pull Up refactoring in Eclipse. That way they can be used by any future creatures we add.

After adding intelligent goblins things should be much more difficult. Play test many times and tweak all the values and behavior you can.


Goblins should also see if they are holding better weapon or armor and switch to it if they are.

Here's the helper methods I came up with:
protected boolean canUseBetterEquipment() {
        int currentWeaponRating = creature.weapon() == null ? 0 : creature.weapon().attackValue() + creature.weapon().rangedAttackValue();
        int currentArmorRating = creature.armor() == null ? 0 : creature.armor().defenseValue();
    
        for (Item item : creature.inventory().getItems()){
            if (item == null)
                continue;
        
            boolean isArmor = item.attackValue() + item.rangedAttackValue() < item.defenseValue();
        
            if (item.attackValue() + item.rangedAttackValue() > currentWeaponRating
                || isArmor && item.defenseValue() > currentArmorRating)
                return true;
        }
    
        return false;
    }

protected void useBetterEquipment() {
        int currentWeaponRating = creature.weapon() == null ? 0 : creature.weapon().attackValue() + creature.weapon().rangedAttackValue();
        int currentArmorRating = creature.armor() == null ? 0 : creature.armor().defenseValue();
    
        for (Item item : creature.inventory().getItems()){
            if (item == null)
                continue;
        
            boolean isArmor = item.attackValue() + item.rangedAttackValue() < item.defenseValue();
        
            if (item.attackValue() + item.rangedAttackValue() > currentWeaponRating
                || isArmor && item.defenseValue() > currentArmorRating) {
                creature.equip(item);
            }
        }
    }

And now you have goblins that will chase you when they can, attack from afar when they can, and switch to better equipment they find. That's a monster that's more intelligent than many rogulike denizens. You can make them even more intellegent by having them remember where the player is. If they can't see the player anymore then they go to that location. This way they won't lose interest in you just because you step out of view for one turn.


These goblins are tough — sometimes too tough. It would be easier if we could regenerate some health. Add this to the creature class:
private int regenHpCooldown;
    private int regenHpPer1000;
    public void modifyRegenHpPer1000(int amount) { regenHpPer1000 += amount; }

Set the regenHpPer1000 to something reasonable in the constructor. I use 10. You could also use constructor injection or call modifyRegenHpPer1000 in our factory to make some monsters regenerate very quickly.

Then create a new method to regenerateHealth and call it as part of the update method.

private void regenerateHealth(){
        regenHpCooldown -= regenHpPer1000;
        if (regenHpCooldown < 0){
            modifyHp(1);
            modifyFood(-1);
            regenHpCooldown += 1000;
        }
    }

Since we have a new stat, we can add to our level up options too.

download the code

Tuesday, October 11, 2011

roguelike tutorial 16: throwing and ranged weapons

Now that we've got our TargetBasedScreen let's make some more things with it. We've already got items so let's make a ThrowScreen. If we add ranged weapons then we could create a FireWeaponScreen.

First let's add another value to represent how much damage is done when an item is thrown.
private int thrownAttackValue;
    public int thrownAttackValue() { return thrownAttackValue; }
    public void modifyThrownAttackValue(int amount) { thrownAttackValue += amount; }

We can set the default to 1 in the constructor and update the StuffFactory to give a value to things that are good for throwing like certain weapons or rocks. Don't forget to update the item class's details method.


Now let's add a couple methods to the Creature class. One throws an item to a location and one damages any creature there.
public void throwItem(Item item, int wx, int wy, int wz) {
        Point end = new Point(x, y, 0);
    
        for (Point p : new Line(x, y, wx, wy)){
            if (!realTile(p.x, p.y, z).isGround())
                break;
            end = p;
        }
    
        wx = end.x;
        wy = end.y;
    
        Creature c = creature(wx, wy, wz);
    
        if (c != null)
            throwAttack(item, c);
        else
            doAction("throw a %s", item.name());
    
        unequip(item);
        inventory.remove(item);
        world.addAtEmptySpace(item, wx, wy, wz);
    }

And now actual attacks with thrown weapons. I'll add half the base attack value of the thrower since thrown weapons should generally do less damage than mele weapons.
private void throwAttack(Item item, Creature other) {
        modifyFood(-1);
    
        int amount = Math.max(0, attackValue / 2 + item.thrownAttackValue() - other.defenseValue());
    
        amount = (int)(Math.random() * amount) + 1;
    
        doAction("throw a %s at the %s for %d damage", item.name(), other.name, amount);
    
        other.modifyHp(-amount);
    
        if (other.hp < 1)
            gainXp(other);
    }


The throwing screen will be interesting because it's the first time we have a double screen scenario. We need a subclass of InventoryBasedScreen to select what we want to throw and then a subclass of a TargetBasedScreen to pick what to throw it at.

The ThrowAtScreen is simple and self explanatory. We can throw at anything we can see that isn't blocked by walls.
package rltut.screens;

import rltut.Creature;
import rltut.Item;
import rltut.Line;
import rltut.Point;

public class ThrowAtScreen extends TargetBasedScreen {
    private Item item;

    public ThrowAtScreen(Creature player, int sx, int sy, Item item) {
        super(player, "Throw " + item.name() + " at?", sx, sy);
        this.item = item;
    }

    public boolean isAcceptable(int x, int y) {
        if (!player.canSee(x, y, player.z))
            return false;
    
        for (Point p : new Line(player.x, player.y, x, y)){
            if (!player.realTile(p.x, p.y, player.z).isGround())
                return false;
        }
    
        return true;
    }

    public void selectWorldCoordinate(int x, int y, int screenX, int screenY){
        player.throwItem(item, x, y, player.z);
    }
}

The ThrowScreen is also a simple InventoryBasedScreen except we need to pass some values that aren't needed by the ThrowScreen but are used by the ThrowAtScreen.
package rltut.screens;

import rltut.Creature;
import rltut.Item;

public class ThrowScreen extends InventoryBasedScreen {
    private int sx;
    private int sy;

    public ThrowScreen(Creature player, int sx, int sy) {
        super(player);
        this.sx = sx;
        this.sy = sy;
    }

    protected String getVerb() {
        return "throw";
    }

    protected boolean isAcceptable(Item item) {
        return true;
    }

    protected Screen use(Item item) {
        return new ThrowAtScreen(player, sx, sy, item);
    }
}

Just add throwing to the HelpScreen and the respondToUserInput method of the PlayScreen and then try it.
case KeyEvent.VK_T: subscreen = new ThrowScreen(player,
        player.x - getScrollX(),
        player.y - getScrollY()); break;

Now you have a use for those rocks that are scattered around.


It's great that we can throw but how about some ranged weapons? Let's update the Item class.
private int rangedAttackValue;
    public int rangedAttackValue() { return rangedAttackValue; }
    public void modifyRangedAttackValue(int amount) { rangedAttackValue += amount; }

Anything that has a non zero rangedAttackValue is a ranged weapon. This way a weapon can have separate attack values for melee, thrown, and ranged combat. We'll keep it simple and say that ranged weapons can hit anything we can see that isn't blocked by terrain.

Add new ranged weapons to the StuffFactory. Bows are good at ranged combat but not very good at hitting things up close.
public Item newBow(int depth){
        Item item = new Item(')', AsciiPanel.yellow, "bow");
        item.modifyAttackValue(1);
        item.modifyRangedAttackValue(5);
        world.addAtEmptyLocation(item, depth);
        return item;
    }
Make sure new weapons gan be created when asking for a randomWeapon.
public Item randomWeapon(int depth){
        switch ((int)(Math.random() * 3)){
        case 0: return newDagger(depth);
        case 1: return newSword(depth);
        case 2: return newBow(depth);
        default: return newStaff(depth);
        }
    }

Creatures need a way to attack with ranged weapons. I'll add half the attack value like with thrown items.
public void rangedWeaponAttack(Creature other){
        modifyFood(-1);
    
        int amount = Math.max(0, attackValue / 2 + weapon.rangedAttackValue() - other.defenseValue());
    
        amount = (int)(Math.random() * amount) + 1;
    
        doAction("fire a %s at the %s for %d damage", weapon.name(), other.name, amount);
    
        other.modifyHp(-amount);
    
        if (other.hp < 1)
            gainXp(other);
    }

The FireWeaponScreen is similar to the ThrowAtScreen. We can fire our weapon at anything we can see that isn't blocked by walls.
package rltut.screens;

import rltut.Creature;
import rltut.Line;
import rltut.Point;

public class FireWeaponScreen extends TargetBasedScreen {

    public FireWeaponScreen(Creature player, int sx, int sy) {
        super(player, "Fire " + player.weapon().name() + " at?", sx, sy);
    }

    public boolean isAcceptable(int x, int y) {
        if (!player.canSee(x, y, player.z))
            return false;
    
        for (Point p : new Line(player.x, player.y, x, y)){
            if (!player.realTile(p.x, p.y, player.z).isGround())
                return false;
        }
    
        return true;
    }

    public void selectWorldCoordinate(int x, int y, int screenX, int screenY){
        Creature other = player.creature(x, y, player.z);
    
        if (other == null)
            player.notify("There's no one there to fire at.");
        else
            player.rangedWeaponAttack(other);
    }
}

Just add firing to the HelpScreen and the respondToUserInput method of the PlayScreen and then try it.
case KeyEvent.VK_F:
        if (player.weapon() == null || player.weapon().rangedAttackValue() == 0)
         player.notify("You don't have a ranged weapon equiped.");
        else
         subscreen = new FireWeaponScreen(player,
             player.x - getScrollX(),
             player.y - getScrollY()); break;


Creatures sometimes use up an item and it no longer exists. Other times they no longer have the item but it still exists in the world. Either way, we need to make sure they no longer have it equipped and that they no longer have it in their inventory. One way to do this is to create two helper methods and call these when possible.
private void getRidOf(Item item){
        inventory.remove(item);
        unequip(item);
    }

    private void putAt(Item item, int wx, int wy, int wz){
        inventory.remove(item);
        unequip(item);
        world.addAtEmptySpace(item, wx, wy, wz);
    }


The attack weapons all have similar bodies so we could use an Extract Method refactoring to move the commonalities to a separate method and use Extract Parameter or Parameterize Method to pass in the differences.

public void meleeAttack(Creature other){
        commonAttack(other, attackValue(), "attack the %s for %d damage", other.name);
    }

    private void throwAttack(Item item, Creature other) {
        commonAttack(other, attackValue / 2 + item.thrownAttackValue(), "throw a %s at the %s for %d damage", item.name(), other.name);
    }

    public void rangedWeaponAttack(Creature other){
        commonAttack(other, attackValue / 2 + weapon.rangedAttackValue(), "fire a %s at the %s for %d damage", weapon.name(), other.name);
    }

    private void commonAttack(Creature other, int attack, String action, Object ... params) {
        modifyFood(-2);
    
        int amount = Math.max(0, attack - other.defenseValue());
    
        amount = (int)(Math.random() * amount) + 1;
    
        Object[] params2 = new Object[params.length+1];
        for (int i = 0; i < params.length; i++){
         params2[i] = params[i];
        }
        params2[params2.length - 1] = amount;
    
        doAction(action, params2);
    
        other.modifyHp(-amount);
    
        if (other.hp < 1)
            gainXp(other);
    }

This particular implementation has the hidden side effect that the message that is passed in must end in a damage indicator since the commonAttack method will add that. It's not good to do things like this too often, but sometimes that happens.

download the code