Difference between revisions of "Complete Roguelike Tutorial, using python+libtcod, part 8"

From RogueBasin
Jump to navigation Jump to search
(first section)
 
m (tabs for spaces and minor wording)
Line 10: Line 10:
== Placing items ==
== Placing items ==


Now that our GUI is all spiffed up, let's put in some more core Roguelike functionality: the inventory! This has been a staple of Roguelikes and RPGs for literally decades. It's a way of gating the player's access to some abilities, and presents an incentive for exploration.
Now that our GUI is all spiffed up, let's put in some more core Roguelike functionality: the inventory! This has been a staple of Roguelikes and RPGs for literally decades. It's a way of gating the player's access to some abilities, and presents an incentive for exploration. Also, why else would you explore a dungeon if not to haul out as much precious items as you can?


We can place some items in each room in pretty much the same way we place monsters, at the end of the ''place_objects'' function:
We can place some items in each room in pretty much the same way we place monsters, at the end of the ''place_objects'' function:




<div style="padding: 5px; border: solid 1px #C0C0C0; background-color: #F0F0F0"><syntaxhighlight lang="python"> #choose random number of items
<div style="padding: 5px; border: solid 1px #C0C0C0; background-color: #F0F0F0"><syntaxhighlight lang="python">   #choose random number of items
num_items = libtcod.random_get_int(0, 0, MAX_ROOM_ITEMS)
    num_items = libtcod.random_get_int(0, 0, MAX_ROOM_ITEMS)
   
for i in range(num_items):
    for i in range(num_items):
#choose random spot for this item
        #choose random spot for this item
x = libtcod.random_get_int(0, room.x1+1, room.x2-1)
        x = libtcod.random_get_int(0, room.x1+1, room.x2-1)
y = libtcod.random_get_int(0, room.y1+1, room.y2-1)
        y = libtcod.random_get_int(0, room.y1+1, room.y2-1)
       
#only place it if the tile is not blocked
        #only place it if the tile is not blocked
if not is_blocked(x, y):
        if not is_blocked(x, y):
#create a healing potion
            #create a healing potion
item = Object(x, y, '!', 'healing potion', libtcod.violet)
            item = Object(x, y, '!', 'healing potion', libtcod.violet)
           
objects.append(item)
            objects.append(item)
item.send_to_back()  #items appear below other objects</syntaxhighlight></div>
            item.send_to_back()  #items appear below other objects</syntaxhighlight></div>




Line 37: Line 37:




<div style="padding: 5px; border: solid 1px #C0C0C0; background-color: #FDECEC"><syntaxhighlight lang="python"> #choose random spot for this monster
<div style="padding: 5px; border: solid 1px #C0C0C0; background-color: #FDECEC"><syntaxhighlight lang="python">       #choose random spot for this monster
x = libtcod.random_get_int(0, room.x1, room.x2)
        x = libtcod.random_get_int(0, room.x1, room.x2)
y = libtcod.random_get_int(0, room.y1, room.y2)</syntaxhighlight></div>
        y = libtcod.random_get_int(0, room.y1, room.y2)</syntaxhighlight></div>




Line 45: Line 45:




<div style="padding: 5px; border: solid 1px #C0C0C0; background-color: #F0F0F0"><syntaxhighlight lang="python"> #choose random spot for this monster
<div style="padding: 5px; border: solid 1px #C0C0C0; background-color: #F0F0F0"><syntaxhighlight lang="python">       #choose random spot for this monster
x = libtcod.random_get_int(0, room.x1+1, room.x2-1)
        x = libtcod.random_get_int(0, room.x1+1, room.x2-1)
y = libtcod.random_get_int(0, room.y1+1, room.y2-1)
        y = libtcod.random_get_int(0, room.y1+1, room.y2-1)
</syntaxhighlight></div>
</syntaxhighlight></div>


Line 63: Line 63:


<div style="padding: 5px; border: solid 1px #C0C0C0; background-color: #F0F0F0"><syntaxhighlight lang="python">class Item:
<div style="padding: 5px; border: solid 1px #C0C0C0; background-color: #F0F0F0"><syntaxhighlight lang="python">class Item:
#an item that can be picked up and used.
    #an item that can be picked up and used.
def pick_up(self):
    def pick_up(self):
#add to the player's inventory and remove from the map
        #add to the player's inventory and remove from the map
if len(inventory) >= 26:
        if len(inventory) >= 26:
message('Your inventory is full, cannot pick up ' + self.owner.name + '.', libtcod.red)
            message('Your inventory is full, cannot pick up ' + self.owner.name + '.', libtcod.red)
else:
        else:
inventory.append(self.owner)
            inventory.append(self.owner)
objects.remove(self.owner)
            objects.remove(self.owner)
message('You picked up a ' + self.owner.name + '!', libtcod.green)</syntaxhighlight></div>
            message('You picked up a ' + self.owner.name + '!', libtcod.green)</syntaxhighlight></div>




Line 79: Line 79:




<div style="padding: 5px; border: solid 1px #C0C0C0; background-color: #F0F0F0"><syntaxhighlight lang="python"> self.item = item
<div style="padding: 5px; border: solid 1px #C0C0C0; background-color: #F0F0F0"><syntaxhighlight lang="python">       self.item = item
if self.item:  #let the Item component know who owns it
        if self.item:  #let the Item component know who owns it
self.item.owner = self</syntaxhighlight></div>
            self.item.owner = self</syntaxhighlight></div>




Line 87: Line 87:




<div style="padding: 5px; border: solid 1px #C0C0C0; background-color: #F0F0F0"><syntaxhighlight lang="python"> item_component = Item()
<div style="padding: 5px; border: solid 1px #C0C0C0; background-color: #F0F0F0"><syntaxhighlight lang="python">           item_component = Item()
item = Object(x, y, '!', 'healing potion', libtcod.violet, item=item_component)</syntaxhighlight></div>
            item = Object(x, y, '!', 'healing potion', libtcod.violet, item=item_component)</syntaxhighlight></div>




Line 94: Line 94:




<div style="padding: 5px; border: solid 1px #C0C0C0; background-color: #F0F0F0"><syntaxhighlight lang="python"> else:
<div style="padding: 5px; border: solid 1px #C0C0C0; background-color: #F0F0F0"><syntaxhighlight lang="python">       else:
#test for other keys
            #test for other keys
key_char = chr(key.c)
            key_char = chr(key.c)
           
if key_char == 'g':
            if key_char == 'g':
#pick up an item
                #pick up an item
for object in objects:  #look for an item in the player's tile
                for object in objects:  #look for an item in the player's tile
if object.x == player.x and object.y == player.y and object.item:
                    if object.x == player.x and object.y == player.y and object.item:
object.item.pick_up()
                        object.item.pick_up()
break
                        break
           
return 'didnt-take-turn'</syntaxhighlight></div>
            return 'didnt-take-turn'</syntaxhighlight></div>





Revision as of 19:08, 5 December 2010

This is part of a series of tutorials; the main page can be found here.

Items and inventory


Placing items

Now that our GUI is all spiffed up, let's put in some more core Roguelike functionality: the inventory! This has been a staple of Roguelikes and RPGs for literally decades. It's a way of gating the player's access to some abilities, and presents an incentive for exploration. Also, why else would you explore a dungeon if not to haul out as much precious items as you can?

We can place some items in each room in pretty much the same way we place monsters, at the end of the place_objects function:


    #choose random number of items
    num_items = libtcod.random_get_int(0, 0, MAX_ROOM_ITEMS)
    
    for i in range(num_items):
        #choose random spot for this item
        x = libtcod.random_get_int(0, room.x1+1, room.x2-1)
        y = libtcod.random_get_int(0, room.y1+1, room.y2-1)
        
        #only place it if the tile is not blocked
        if not is_blocked(x, y):
            #create a healing potion
            item = Object(x, y, '!', 'healing potion', libtcod.violet)
            
            objects.append(item)
            item.send_to_back()  #items appear below other objects


For this to work, we must define the new constant MAX_ROOM_ITEMS = 2 at the top. Later we'll expand this with a few magic scrolls in addition to the healing potions; this is the spot to add any items you want in your game. The healing potions don't have any special components for now; we'll get to that in a second.

The limits of the random position of the items (passed to random_get_int) are a bit different than for the monsters. Actually, the monsters' coordinates have been slightly off the whole time! They should be changed from this:


        #choose random spot for this monster
        x = libtcod.random_get_int(0, room.x1, room.x2)
        y = libtcod.random_get_int(0, room.y1, room.y2)


To this:


        #choose random spot for this monster
        x = libtcod.random_get_int(0, room.x1+1, room.x2-1)
        y = libtcod.random_get_int(0, room.y1+1, room.y2-1)


Which is one tile tighter than the room's walls in every direction. That's because the room's rectangle, as we defined it earlier, includes its walls too (oops!). Anyway, when the old code decided to place a monster on the walls, it wouldn't get created due to the is_blocked check, so there were less monsters on average -- now the game got a little harder! The healing potions should balance this effect, but you can always tweak the values to your liking of course.

After that embarrassing revelation, let's define the inventory! This goes before the main loop:


inventory = []


Simple enough; the inventory is a list of items, and it starts empty. Now the Item component -- it will hold all data and functionality that makes an object behave like an item. For it to make its way into the player's inventory, we'll start by giving it a pick_up method.


class Item:
    #an item that can be picked up and used.
    def pick_up(self):
        #add to the player's inventory and remove from the map
        if len(inventory) >= 26:
            message('Your inventory is full, cannot pick up ' + self.owner.name + '.', libtcod.red)
        else:
            inventory.append(self.owner)
            objects.remove(self.owner)
            message('You picked up a ' + self.owner.name + '!', libtcod.green)


The limit of 26 is because later, in the inventory screen, items will be selected by pressing a key from A to Z, and there are 26 letters. You could overcome this restriction by implementing "pages" in the inventory, or a fancier interface with scrollbars. That would be a bit harder, so we'll stick to this for now. You could also assign weights to the items and limit the total weight here, as some games do.

This component must be accepted by the Object 's __init__ method, like all other components. Just add another parameter to it item=None, and an initialization similar to the other components:


        self.item = item
        if self.item:  #let the Item component know who owns it
            self.item.owner = self


Now that we have an Item component, you can add it to the healing potion in place_objects:


            item_component = Item()
            item = Object(x, y, '!', 'healing potion', libtcod.violet, item=item_component)


How does the player pick up an item? It's very easy: just test for another key in the handle_keys function. If it's pressed, look for an item under the player and pick it up. The new code goes between the else and the return 'didnt-take-turn' line:


        else:
            #test for other keys
            key_char = chr(key.c)
            
            if key_char == 'g':
                #pick up an item
                for object in objects:  #look for an item in the player's tile
                    if object.x == player.x and object.y == player.y and object.item:
                        object.item.pick_up()
                        break
            
            return 'didnt-take-turn'


You can test it out now! There will be a few potions scattered around, and you'll get a message when you pick them up by pressing G. The inventory is still invisible though.

...