Tuesday, September 27, 2011

roguelike tutorial 12: weapons and armor

Time for some weapons and armor.

Since we have a very simple Attack value and Defense value for creatures, let's use that for our weapons and armor. Go ahead and add that to the Item class.

private int attackValue;
  public int attackValue() { return attackValue; }
  public void modifyAttackValue(int amount) { attackValue += amount; }

  private int defenseValue;
  public int defenseValue() { return defenseValue; }
  public void modifyDefenseValue(int amount) { defenseValue += amount; }


And create some new items in our factory class.

public Item newDagger(int depth){
    Item item = new Item(')', AsciiPanel.white, "dagger");
    item.modifyAttackValue(5);
    world.addAtEmptyLocation(item, depth);
    return item;
  }

  public Item newSword(int depth){
    Item item = new Item(')', AsciiPanel.brightWhite, "sword");
    item.modifyAttackValue(10);
    world.addAtEmptyLocation(item, depth);
    return item;
  }

  public Item newStaff(int depth){
    Item item = new Item(')', AsciiPanel.yellow, "staff");
    item.modifyAttackValue(5);
    item.modifyDefenseValue(3);
    world.addAtEmptyLocation(item, depth);
    return item;
  }

  public Item newLightArmor(int depth){
    Item item = new Item('[', AsciiPanel.green, "tunic");
    item.modifyDefenseValue(2);
    world.addAtEmptyLocation(item, depth);
    return item;
  }

  public Item newMediumArmor(int depth){
    Item item = new Item('[', AsciiPanel.white, "chainmail");
    item.modifyDefenseValue(4);
    world.addAtEmptyLocation(item, depth);
    return item;
  }

  public Item newHeavyArmor(int depth){
    Item item = new Item('[', AsciiPanel.brightWhite, "platemail");
    item.modifyDefenseValue(6);
    world.addAtEmptyLocation(item, depth);
    return item;
  }

  public Item randomWeapon(int depth){
    switch ((int)(Math.random() * 3)){
    case 0: return newDagger(depth);
    case 1: return newSword(depth);
    default: return newStaff(depth);
    }
  }

  public Item randomArmor(int depth){
    switch ((int)(Math.random() * 3)){
    case 0: return newLightArmor(depth);
    case 1: return newMediumArmor(depth);
    default: return newHeavyArmor(depth);
    }
  }

Don't forget to add them to the newly created game in the PlayScreen createItems method.

If you play you should be able to see them and carry them around.


If we want to use them then we need to add some methods to the creature class to equip and unequip weapons and armor. For now, creatures can wield one weapon and wear one pice of armor at a time. If you want separate armor slots for helmet, rings, shoes, etc, you can do that too. I'm also going to use the same methods to deal with armor or weapons.

private Item weapon;
  public Item weapon() { return weapon; }

  private Item armor;
  public Item armor() { return armor; }
public void unequip(Item item){
      if (item == null)
         return;
  
      if (item == armor){
          doAction("remove a " + item.name());
          armor = null;
      } else if (item == weapon) {
          doAction("put away a " + item.name());
          weapon = null;
      }
  }
public void equip(Item item){
      if (item.attackValue() == 0 && item.defenseValue() == 0)
          return;
  
      if (item.attackValue() >= item.defenseValue()){
          unequip(weapon);
          doAction("wield a " + item.name());
          weapon = item;
      } else {
          unequip(armor);
          doAction("put on a " + item.name());
          armor = item;
      }
  }

And make sure that we unequip anything we eat or drop.

public void eat(Item item){
      if (item.foodValue() < 0)
         notify("Gross!");
  
      modifyFood(item.foodValue());
      inventory.remove(item);
      unequip(item);
  }

public void drop(Item item){
    if (world.addAtEmptySpace(item, x, y, z)){
        doAction("drop a " + item.name());
        inventory.remove(item);
        unequip(item);
    } else {
        notify("There's nowhere to drop the %s.", item.name());
    }
}

The easiest way to use our new equipment when calculating our overall attack and defense values is just to add them to the creature's getters.
public int attackValue() {
    return attackValue
     + (weapon == null ? 0 : weapon.attackValue())
     + (armor == null ? 0 : armor.attackValue());
  }

  public int defenseValue() {
    return defenseValue
     + (weapon == null ? 0 : weapon.defenseValue())
     + (armor == null ? 0 : armor.defenseValue());
  }


And now create an EquipScreen.
package rltut.screens;

import rltut.Creature;
import rltut.Item;

public class EquipScreen extends InventoryBasedScreen {

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

  protected String getVerb() {
    return "wear or wield";
  }

  protected boolean isAcceptable(Item item) {
    return item.attackValue() > 0 || item.defenseValue() > 0;
  }

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

Wasn't that easy?

All that's left is making the 'w' wear or wield items in the PlayScreen. I prefer having one key for both rather than one for armor and another for weapons. If you'd rather have different keys then you can do that.
case KeyEvent.VK_W: subscreen = new EquipScreen(player); break;

And now you can find and use weapons and armor. Play around with different attackValues, defenseValues, and hit points. You can have 3 or 4 weapons or 300 weapons. Try changing how abundant weapons and armor are or maybe have some more common than others.


One advantage of having all our items be the same class but have different values is that an item can be more than one thing, e.g. you could make an edible weapon and the player would be able to eat or wield it with no extra code or you could have have a weapon that increases attack and defense.
public Item newEdibleWeapon(int depth){
    Item item = new Item(')', AsciiPanel.yellow, "baguette");
    item.modifyAttackValue(3);
    item.modifyFoodValue(50);
    world.addAtEmptyLocation(item, depth);
    return item;
  }

You can't do that with Weapon, Food, and Armor subclasses.


Wouldn't it also be nice if the inventory screens told us what we have equipped so we don't eat the armor we're wearing or try to wear something we're already wearing? Here's one possible update to the InventoryBasedScreen:
String line = letters.charAt(i) + " - " + item.glyph() + " " + item.name();
    
  if(item == player.weapon() || item == player.armor())
      line += " (equipped)";
    
  lines.add(line);
Maybe the EquipScreen shouldn't let us equip what we're already using. Or maybe wearing or wielding what's already equipped should un-wear or un-weild it? That way the 'w' key can equip or unequip. It's your game so it's up to you. Implementing those is left as an exercise.

download the code

4 comments:

  1. Quick question, I cant seem to figure out how to change abundance of items. I have no clue what I'm not seeing.

    ReplyDelete
  2. Hey Trystan. Not sure if you are still looking at older comments but there is a mistake in the equip method that makes it equip armor to the weapon slot.

    For anyone reading this who has the same problem change the line in the else block of the equip method from weapon = item; to armor = item;

    ReplyDelete
  3. It has brought around all those instances and probabilities for the students which must have even been followed by the individual for the better cause and success.

    ReplyDelete