Chapter 5
Items and Loot

What’s the first thing you see when you picture a fighter in your mind? Do you see armor, a sword and shield, or maybe the fighter shooting an arrow from a bow? Perhaps you imagine him on horseback, charging with a lance. Regardless of the picture you have, more than likely the first thing you noticed wasn’t the person, but his equipment. That makes sense, since a fighter without his gear is almost useless in an RPG (unless he’s a martial artist, but that’s another chapter).

Most RPGs these days have so many different items in them that a player can’t move 20 feet without stumbling over a crate that’s filled with something, even if it’s mostly useless junk that he just sells for a few coins so that he can buy whatever it is he really needs. Then there’s the MMORPG type of item distribution system where rats will drop full pieces of plate armor. While that may not be the best way to go, you’ll want to get items to players in some way. I think it does make sense for NPCs and creatures to have items that the player can pick up after they’re defeated, but it should be items that they would really have on them; the armor and weapons that they used against the player, money and other trinkets that they may be carrying around, etc.

Many gamers play RPGs because they like to collect all the high-powered gear. If your RPG doesn’t have some cool stuff in it, you’ll lose a good number of players. And let’s face it — all of that cool stuff is just fun to play with! Fortunately it’s not that hard to get items into your game.

In this chapter we’ll implement classes that will be used to handle all types of items — armor, weapons, and miscellaneous things like scrolls, rings, and potions. We’ll also develop an Inventory class to manage all of the stuff a character will end up lugging around.

The BaseItem Class

Let’s start with our base class for all items, aptly named BaseItem:

Listing 5-1


abstract public class BaseItem : Object
{
protected int _id;

protected string _name;
protected string _description;
protected float _weight;
//Used for different purposes depending on the type of derived item
protected string _tag;

protected Texture2D _picture; //used in inventory/vendor dialogs

}

The first thing to notice is that the class is declared as abstract, meaning instances of the class cannot be created. A class has to inherit from it and instances of that inherited class created. We’ll do quite a bit of that shortly, so don’t worry. The second thing to notice is that the members are declared as protected, rather than private, which we have to do if we want the derived classes to be able to use them.

The _id member will be our link to the item from outside code. Since we could potentially have items with somewhat longish and interesting names like “Flaming Platinum Sword of Vengeance vs. Trolls,” we can’t really use the _name member as we do with other classes.

Speaking of the name, there are several ways to handle our flaming sword example. Many RPGs use one or more adjectives in the name of the weapon, but that doesn’t mean you have to store those adjectives in the name. You could create sets of metadata that you attach to various items. Some metadata might not make sense for some items, so you could restrict certain words from being used with those items. For example, you might not want to allow a “Flaming Platinum Potion of Vengeance vs. Trolls” to be created. If you did, I don’t think I would want my character to drink it.

Metadata: Simply put, this is a fancy word that means “data about data.”

An entity can only lift or carry so much weight, so our items have to have a _weight member. A player would not be allowed to add items to a character’s inventory if the combined weight of all the items in the inventory is over the max the character can handle. We’ll see this later in the chapter when we create the Inventory class. There we’ll also see how the _picture member is used.

The _tag member will be used in various ways. For example, it’s a nice way to allow a bit more flexibility in how items are handled and used.

Only one item will be derived directly from BaseItem — the Money class:

Listing 5-2


public class Money : BaseItem
{
public const int CoinsPerPound = 10;

private int _amount;

//Overrides since the strength required changes as the amount changes
new public byte StrengthRequired
{
get { return (byte)((_amount / CoinsPerPound) /
Stat.PoundsPerStatPoint); }
}

public static byte GetStrengthRequired(int amount)
{
return (byte)((amount / CoinsPerPound) /
Stat.PoundsPerStatPoint);
}

public int Total()
{
return _amount;
}

public Money()
{

}

public static int GetWeight(int amount)
{
return (int)(amount / CoinsPerPound);
}

public int TotalWeight
{
get { return GetWeight(_amount); }
}

//Called for things like buying items
public void Subtract(int amount)
{
if (amount <= _amount)
_amount -= amount;
}

//Parameter is declared as ref
//so we can modify it to let the calling code know
//that we couldn't hold the full amount.
//This should never happen, however,
//as the weight alone should limit what an entity can carry
public void Add(ref int amount)
{
if (amount + _amount <= int.MaxValue)
{
_amount += amount;
}
else
{
amount = (int)(int.MaxValue - _amount);
_amount = int.MaxValue;
}
}

//Called for things like dropping on the ground, etc.
//Added simply for making sense in the context for which it's used
public void Drop(int amount)
{
Subtract(amount);
}

public void Give(ref int amount, ref Entity target)
{
if (amount <= _amount)
{
target.AddMoney(ref amount);

}
}
}

We can see that, in the game world, 10 coins would weigh one pound. This will be useful later on when we implement our inventory. Some RPGs may allow the character to carry around millions of coins as well as dozens of weapons, armor, and other items, but if you want to model a realistic inventory, the CoinsPerPound member will help ensure that characters can’t carry around enough money to buy out a shopkeeper’s entire inventory. We’ll add the capability to ignore weight into the inventory, however, so that characters can carry around thousands of pounds of gear if you wish to allow it.

If a developer needs to know how much strength is required to handle all of the money a character is currently carrying or any amount of coins, the StrengthRequired functions will tell him. There are also functions to get the weight of the money a character is carrying or a certain amount of coins.

The remaining four functions allow money to be given or taken away from an entity in different situations, such as trading with or giving money to another entity, buying or selling items, or just dropping it on the ground.

The Item Class

All remaining types of items will be derived from the Item class:

Listing 5-3


public abstract class Item : BaseItem
{
protected ItemType _type;

protected int _cost;
protected bool _magical;
protected AlignmentBitwise _alignments;

protected bool _questItem;
protected byte _questItemPiece;
protected int _questID;
protected bool _questReward;

protected bool _droppable;
protected bool _tradeable;

protected int _baseHP;

protected List<Spell> _spells;

protected List<Bonus> _defBonuses;
protected List<Bonus> _offBonuses;
protected List<Bonus> _miscBonuses;

//Is item automatically put in character's hand when picked up
protected bool _autoReady;

protected bool _repairable;

protected bool _unique;

//ID of the skill required to use this
protected int _skillRequired;

//All classes that derive from this one must implement this function
public abstract bool Use(ref Object target, ref Entity wielder);
}

The types of items we’ll be dealing with are the following:

Listing 5-4


public enum ItemType
{
Armor,
Chest,
Key,
Potion,
Scroll,
Shield,
Weapon,
Money //Placed out of order since we don't create money
//in the editor
}

Of course, you’ll probably want to add more items for your game. The nice thing is that you just have to add the item to this enum and you’ll be able to create specific items of this type in the editor, as we’ll see later on in the chapter.

The _cost member is the amount in gold pieces needed to buy the item from an NPC. The gold piece will be the standard unit of money. Many RPGs go this route, as it’s easy to manage. Other RPGs and MMORPGs use several denominations of coins. Balancing costs is much more difficult when doing this, but gives greater flexibility.

The _magical member is set to true if an item possesses magical qualities. This is only for items that are not normally magical, such as weapons or armor. Scrolls, potions, and the like are already assumed to be magical. The member will be ignored for items like these, although it might be nice to explicitly set it to true. If the item is magical, the _spells member is used to hold the magical properties of the item.

In some RPGs, the use of certain items is limited to specific races or alignments. The _alignments member allows us to do this. The enum for this member looks like this:

Listing 5-5


[Flags]
public enum AlignmentBitwise
{
None = 0,
Good = 1,
Neutral = 2,
Evil = 4
}

The next four members are used for a specific type of item — one that is the target or reward of a quest. Think King Arthur’s quest to find Excalibur to understand what these are used for. _questItem tells us if the item is the target of a quest, _questItemPiece specifies the piece of the quest item (say, Excalibur has been broken into pieces and has to be found and reassembled), _questID is the ID of the quest the item belongs to, and _questReward indicates whether the item is a reward that is given to a character for completing a quest.

The next two members are used more often in MMORPGs than in normal RPGs, but you might want to use the functionality in a non-MMORPG, especially if you implement multiplayer support. Rewards from quests in MMORPGs are normally not tradeable between characters and cannot be dropped on the ground for another character to pick up. Once the player picks up or accepts a quest reward item, he can use it, keep it in his inventory, or destroy it. The _droppable and _tradeable members help implement this type of functionality.

Another feature of some RPGs is to make items wear out over time or be able to be damaged or destroyed. In order to do the latter, we need to know how much damage an item can take. The _baseHP member will handle this. You may want to go even further and implement functionality that makes the item less useful the more it gets damaged, simulating an effect such as a sword losing its sharpness the more it’s used in combat. If you do so, the _repairable member can be used to tell whether the item can be restored to full effectiveness.

The _autoReady member will place the item either in the character’s hand if it’s a weapon or shield or at the proper location on the character’s body if it’s a piece of armor or jewelry and not just an item that exists in that location.

The _unique member simply lets the player know if the item is the only one of its kind in the world with those exact properties. In almost every case, this item’s _droppable and _tradeable members will also be set to false. Once the player picks up the item, he can keep it in his inventory, put it in a private storage area (MMORPGs usually give players places in a bank or something similar that no other player can access), or destroy it.

The _skillRequired member should be fairly self-explanatory. There may be cases where the player isn’t quite sure whether a long sword can be wielded one-handed, so he wouldn’t know whether to train in one- or two-handed sword skill (assuming you broke down skills that far). This member helps alleviate any worry about that.

The Use function will be implemented in the classes that inherit from the Item class, so we’ll look at how each handles implementing it when we look at those classes. For weapons, they’ll be very similar, but other types of items will differ a bit.

The Weapon Class

Let’s start with weapons then. This class will look very simple, and it should. Most of the work has been done for us in the Item class, as it should be.

Listing 5-6


public class Weapon : Item
{
private WeaponType _weaponType;
//array of Damager objects
private List<Damager> _damagers;

public WeaponType Type
{
get { return _weaponType; }
set { _weaponType = value; }
}

public List<Damager> Damagers
{
get { return _damagers; }
set { _damagers = value; }
}

public void AddDamager(Damager item)
{
if (_damagers == null)
_damagers = new List<Damager>();

_damagers.Add(item);
}

public void AddDamager(DamageType type, DieType amount, String affects)
{
Damager item = new Damager();

item.Affects = affects;
item.DamageAmount = amount;
item.Type = type;

if (_damagers == null)
_damagers = new List<Damager>();

_damagers.Add(item);
}

protected int GetDamage(byte damageType)
{
int damage = 0;

for(int i = 0; i < _damagers.Count; i++)
{
if (_damagers[i].Type == (DamageType)damageType)
{
damage = GlobalFunctions.GetRandomNumber(
_damagers[i].DamageAmount);
break;
}
}

return damage;
}

public override bool Use(ref Object target, ref Entity wielder)
{

//not used as attacking with a weapon is handled
//by the combat manager

return true;
}

}

The members of the WeaponType enum will be simple and can easily be expanded upon:

Listing 5-7


public enum WeaponType
{
OneHandSword,
TwoHandSword,
Dagger,
Staff,
Mace,
Bow,
Crossbow
}

There is at least one type of weapon for each entity. Both Bow and Crossbow are included so the differences in speed and damage can be tested and chosen between.

The _damagers collection is used to determine what type of damage the weapon inflicts. It’s a simple structure:

Listing 5-8


[Serializable]
public struct Damager
{
public DamageType Type;
//Format = "d<x> or <x>"
public string DamageAmount;
//The type of entity the damage affects, empty for all types
public string Affects;
}

The current types of damage are as follows:

Listing 5-9


public enum DamageType
{
Crushing,
Piercing,
Slashing,
Fire,
Water,
Magical,
Disease,
Poison
}

Weapons will normally only use the first three values, but if the weapon is magical, any of them may be added to the normal type of damage. The “Flaming Platinum Sword of Vengeance vs. Trolls” we talked about previously could deal both Piercing and Slashing damage (depending on the type of attack used if you allow some swords to have more than one attack type) as well as Fire damage.

The DamageAmount member can have one of two formats — d<x> to specify one of the DieType values, meaning the weapon does a random amount of damage from 1 to some number, or <x>, meaning the weapon does a set amount of damage if an attack with it succeeds.

The Affects member is a type of entity for our purposes (although you could have a weapon that does special damage to inanimate objects, such as Stonecutter from Fred Saberhagen’s Lost Swords series). Our example sword would have this member set to “Troll” and deal the damage specified by DamageAmount in addition to the regular damage the weapon does.

The Armor Class

Listing 5-10


public class Armor : Item
{
private ArmorArea _location;
private short _defValue;
private bool _equipped;

public ArmorArea Location
{
get { return _location; }
set { _location = value; }
}

public short DefensiveValue
{
get { return _defValue; }
set { _defValue = value; }
}

public bool Equipped
{
get { return _equipped; }
}

public override bool Use(ref Object target, ref Entity wielder)
{
//Use for this class means to equip,
//so set member if it's being equipped
short loc = (short)target;

if (loc > -1)
{
//the value tells us where it's trying to be equipped
//so check it
//for instance, a piece is dragged onto the wrong location
//in the character screen
if (loc != (short)_location)
_equipped = true;

}
else
_equipped = false;

return equipped;
}
}

Each piece of armor has only one location on the body where it can be worn. The ArmorArea enum gives us the possible locations:

Listing 5-11


public enum ArmorArea
{
None=-1,
Head,
Neck,
Chest,
LeftUpperArm,
UpperArms,
LeftLowerArm,
LowerArms,
LeftWrist,
Wrists,
LeftHand,
//Could add fingers here for rings
Hands,
RightUpperArm,
RightLowerArm,
RightWrist,
RightHand,
//Could add fingers here for rings
Waist,
Groin,
LeftUpperLeg,
UpperLegs,
LeftKnee,
Knees,
LeftLowerLeg,
LowerLegs,
LeftFoot,
Feet,
RightUpperLeg,
RightKnee,
RightLowerLeg,
RightFoot,
LeftHandEquipped, //used for the item held in the left hand
RightHandEquipped //used for the item held in the right hand
}

Note that the LeftHandEquipped and RightHandEquipped values are not for armor but for items that are being held in the indicated hand, which is why we’ve put them at the end of the list.

The _defValue member indicates how much damage the piece absorbs from a blow, subtracting the amount of damage the entity receives. This value could decrease as the piece is damaged, perhaps by a percentage of the remaining hit points the piece has.

The _equipped member tells us if the piece of armor is being worn by the entity. Since the object will still be stored in the entity’s inventory, this member is needed to show what the entity is wearing. When a piece of armor is put on, the object could be moved to another member of the Entity object and the member done away with. Either way, to determine if an area of the entity is covered, we’ll still have to look at a member of the entity class, so it’s just easier to me to leave it in the inventory.

The Chest Class

What would an RPG be without some chests in which to find loot? Technically, you could use this class for just about any object that can contain items (barrel, furniture, etc.), especially if it’s the type of object that can be locked.

The class isn’t very complex and could be made even simpler if you don’t need to randomly generate the items it contains or want to be able to set a trap. (Where would the fun be in that, though; after all, thieves need something to do to make them feel important.)

Listing 5-12


public class Chest : Item
{
private bool _locked;

//tag or name property of the key item required to open the lock,
//set to "key" if a generic key item will open the lock
private string _keyRequired;

private bool _open;

private bool _randomlyGenerate;
private ItemType _randomType;
private int _maxValue;

private bool _trapped;
private TrapType _trapType;
private Spell _trapSpell;
private Difficulty _trapLevel;
private int _damage;

private List<Item> _items;

public bool IsOpen
{
get { return _open; }
set
{
_open = value;
if (_open)
_locked = false;
}
}

public override bool Use(ref object target, ref Entity wielder)
{
//if the chest is already open return
if (_open)
return true;

//target in this case is null, since the chest is the target
//wielder is the entity trying to open the chest
//call PickLock if the chest is locked
if (_locked)
{
if (!wielder.HasItemByTagOrName(_keyRequired))
return false;

_open = true;
}

return true;
}

public bool PickLock(ref Entity entity)
{
bool ret = true;

//function should never be called for an unlocked chest
//but just in case...
if (_locked)
{
int ID = GlobalFunctions.GetIDForSkillName("Pick Lock");
if (ID > 0)
{
if (entity.HasSkillByID(ID))
{
Object chest = (Object)this;

ret = entity.UseSkill(ref chest, ID, _trapLevel);
}
}
}

return ret;
}

public List<Item> GetItems()
{
if (_randomlyGenerate)
{
int value = 0;
Item item = null;

do
{
//get a random item type


//get a random item of that type


//add the value of that item to the value of the items
//in the list
value += item.Cost;

}
while (value < _maxValue);

return _items;
}
else
{
if (!(_items == null))
return _items;
else
throw (new Exception("Invalid items list"));
}
}
}

There are a couple of boolean values that indicate whether the chest is open, locked, or trapped. If it’s locked, the _keyRequired member tells what key will open up the chest. You can use either the _name or _tag property of the key to determine what key is needed.

If there is a trap around the chest, there are a couple of members to look at. The _trapType can be one of the following:

Listing 5-13


public enum TrapType
{
None,
Missile, //normal missile-type
Gas,
Magic, //spell is cast
Explosion
}

If the trap is a Magic trap, the _trapSpell member will hold the spell to be cast if the trap is tripped. The _trapLevel member is used against the level of the Disarm Trap skill of the person attempting to disarm the trap. The _damage member is used if the trap is set off and it is not a Magic trap.

The items in the chest are contained in the _items member. This member is set either at design time or, if the _randomlyGenerate member is set to true, when the chest is opened and inspected. The _randomType member is the type of item to pick from. For our purposes it can be one of the following:

Listing 5-14


public enum ItemType
{
Armor,
Chest,
Key,
Potion,
Scroll,
Shield,
Weapon,
Money //Placed out of order since we don't create money
//in the editor
}

Obviously, the Chest item in the enum won’t be used unless the Chest class is being used as some other kind of item. That would be up to the level designer to determine.

The _maxValue member is used to limit the value of the items in the chest if the contents are being randomly generated. The actual code to do this will be filled in later once we’ve determined how we’ll access items in-game.

The Item class’s Use method here is overridden to mean “open the chest.” If the chest is already open, the function simply returns true. If the chest is locked, the entity is checked to see if he has the key using the HasItemByTagOrName function:

Listing 5-15


public bool HasItemByTagOrName(string tag)
{
foreach (Item item in _inventory.Items)
{
if (item.Name == tag || item.Tag == tag)
return true;
}

return false;
}

If the chest is locked and the entity does not have the key, the lock has to be picked, which means the entity has to have the Pick Lock skill:

Listing 5-16


public bool PickLock(ref Entity entity)
{
bool ret = false;

//function should never be called for an unlocked chest
//but just in case...
if (_locked)
{
int ID = GlobalFunctions.GetIDForSkillName("Pick Lock");

if (ID > 0)
{
if (entity.HasSkillByID(ID))
{
Object chest = (Object)this;

ret = entity.UseSkill(ref chest, ID, _trapLevel);
}
}
}

return ret;
}

The EntityItem Class

Since an entity could conceivably be carrying several of an item and there could be many entities in a level carrying the same item, we obviously don’t want to create instances of an item every time. The only pieces of data we need are the ID and the current hit points for the item. So we create a simple class that we’ll store in the entity’s inventory:

Listing 5-17


public class EntityItem : Item
{
private int _id;
private int _curHP;

public EntityItem(int id, int curHP)
{
_id = id;
_curHP = curHP;
}

public int ID
{
get { return _id; }
set{}
}

public int CurHP
{
get { return _curHP; }
set { if (value >= 0) _curHP = value; }
}

public bool Useable()
{
return (_curHP > 0);
}

public void Damage(int amount)
{
_curHP -= amount;

if (_curHP < 0)
_curHP = 0;
}

public void Repair(int amount)
{
_curHP += amount;

if (_curHP > GlobalData.Items[_id].BaseHP)
_curHP = GlobalData.Items[_id].BaseHP;
}

//repair to a percentage of the BaseHP
public void Repair(float percentage)
{
_curHP = (int)(GlobalData.Items[_id].BaseHP * percentage);
}
}

If an item’s hit points drops to 0, the item is not usable until it’s repaired. Alternatively, you could have the item be destroyed when its hit points reaches 0 and remove it from the inventory. Additionally, the item could be usable at a reduced effectiveness, perhaps expressed as a percentage of _curHP to _baseHP. Many RPGs show the current state of the item in the game’s GUI so the player knows when an item has to be repaired, often turning that indicator from green to yellow to red to show the item’s decreased state of repair.

Before allowing an item to be used, the Useable function should be called. If the item is not usable, you could display a message to the player and perhaps even move the item from being held to being stored in the player’s inventory.

The Inventory Class

The player will need a way to keep track of all the loot he has, as well as what pieces of equipment he’s wearing or using. Most games use separate GUI pieces for the two. We’ll include both pieces of data in one class to try to make it a bit easier to manage, both from a code standpoint and a GUI standpoint (as we’ll see later on in Chapter 9).

Many games make inventory management almost a subgame, with items having to be shuffled around in the inventory to make room for other items that are picked up. Even if the character actually has room in his inventory if things were shuffled around, some games will not allow you to pick up an item or will drop the item on the ground instead of rearranging the inventory for you. If the player isn’t paying attention and assumes the item is in his inventory when this happens, it could mean the item may be lost forever when the player moves to a different area or level. This makes absolutely no sense to me. Whether it’s a lack of desire to code a decent inventory dialog that can automatically rearrange items to make room as other items are picked up or something else, one of the things I hate most about many RPGs is inventory management. There are many ways to make it easy for the player to find and use items in his inventory. Rather than force you to accept what I think is the best solution, however, a generic class will be implemented that can easily be inherited from and made to function as you think best.

Listing 5-18


public class Inventory
{
//can't use a Dictionary here because the inventory
//could contain multiples of the same item, therefore the same id
private List<EntityItem> _items;
private Dictionary<int, int> _equippedItems; //Key is an ArmorArea

public List<EntityItem> Items
{
get { return _items; }
set { _items = value; }
}

public float TotalWeight()
{
float weight = 0.0f;

foreach (Item item in _items)
weight += item.Weight;

return weight;
}

public void Add(EntityItem item)
{
//check to see if entity is overweight
//needs to be done in the Entity class
_items.Add(item);
}

public EntityItem Get(int id)
{
foreach (EntityItem item in _items)
if (item.ID == id)
return item;

return null;
}

public void Remove(int id)
{
//if the item is equipped remove it from the _equipped list
if (_equippedItems.ContainsValue(id))
{
foreach (KeyValuePair<int, int> item in _equippedItems)
{
if (item.Value == id)
{
_equippedItems.Remove(item.Key);
break;
}
}
}

_items.Remove(_items[id]);
}

public Texture2D GetArmorAreaPicture(ArmorArea area)
{
foreach (Item item in _items)
if (item.ID == _equippedItems[(int)area])
return item.Picture;

return null;
}
}

As is noted in the code comment, a List<EntityItem> object is being used rather than a Dictionary<int, EntityItem> since a character can carry more than one of a particular item. Using a dictionary in this case would throw an exception when attempting to add an item that already exists to the dictionary. One way around this would be to include a _count member in the EntityItem class to show how many of that item are being carried. This is certainly a viable alternative. The graphic used to display the item in the inventory window could also have the number drawn on top of it with the value of the _count member. If such a setup is more to your liking, it wouldn’t take long to make the necessary changes.

The _equippedItems collection is where we’ll use the LeftHandEquipped and RightHandEquipped values of the ArmorArea enum. Sure, the items being held by the character could have been tracked some other way, but setting it up this way was quick and easy. It means two less members of the inventory to have to deal with at the slight expense of having the ArmorArea enum be less than accurate. Sometimes trade-offs like this are used, and it’s not necessarily a wrong thing in my opinion.

When we get to Chapter 9, we’ll create an interface that is used to display the items in the inventory, both equipped and not, and allow the player to move things from the inventory to a location on the character’s body. Since the Inventory class is fairly non-implementation-specific, there could be a number of ways to create the interface.

Summary

A character can now be created to have everything he needs to survive in the game world. That’s good because in the next chapter we’re going to allow the character to be tested by implementing a combat system.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset