Flylib.com

Books Software

 
 
 

MUD Game Programming (Premier Press Game Development) - page 99

[ LiB ]

Summary

This chapter gave you a brief glimpse of the underlying concepts of entity storage and management concepts, which should be familiar to you from learning the SimpleMUD.

I've expanded on and improved those concepts in a number of areas, making the system more reusable and flexible, and because of this, I was able to reduce the code at higher levels. Whenever I want to make a database load a directory of entities, all I need to do is issue a command like this: DB.LoadDirectory( "data/blah/" ) .

In the next chapter, I'll show you the complete implementations of all of the entity classes, but I keep that simple as well. The actual entities are simple classes that don't have much to do with the actual game, except that they contain data. The main idea I'm trying to promote here is keeping the hard-coded stuff to a minimum, while making the actual game as flexible as possible, without making it too flexible.

[ LiB ]
[ LiB ]

Chapter 13. Entities and Databases Continued

The previous chapter described the base entity and database classes and outlined the accessor classes, but didn't show you how to actually implement the final entity or database classes. This chapter does so. The good news is that since most of the work has already been accomplished in the base classes, the final entity and database classes are really quite simple.

In this chapter, you will learn to:

  • Use databanks to make your entities flexible

  • Work with the six entity classes: accounts, characters , items, rooms, portals, and regions

  • Use entity databases to store files to disk

  • Use entity accessors as iterators

[ LiB ]
[ LiB ]

Databanks

Before discussing entities, I'd like to show you a special class. In the previous chapter, I mentioned that there is a mixin class called DataEntity , which wraps around a databank .

A databank gives flexibility to the BetterMUD. I'm sure you're used to figuring out what kinds of data you need, programming the data, and then realizing that you might not need an attribute or two. If you create instead a flexible structure that can store an unlimited amount of data, you can easily add variables to your characters while the game is running. If you think this idea is cool, just wait until I show you Python.

The flexible structure I've described requires storing objects with arbitrary names , and what better structure to store them in than an std::map ? If you don't know this already, you should learn that std::map s used with std::string s are practically the coolest thing you can ever do in C++. Examine this code segment:

std::map<std::string, int> intbank;
intbank["pie"] = 20;
intbank["cool"] = 30;
int a = intbank["pie"];   // 20
a = intbank["cool"];      // 30

Pretty cool, isn't it? You can insert as many items as you want, and since it's a map, you'll have some decent performance ( O(log n ) for those algorithm-obsessed folks) when inserting or retrieving items. Sure, it's not nearly as fast as accessing variables directly by a memory offset (which is the way that precompiled variables are accessed), but I believe that the time has finally come when the benefits of having such an extensible system far outweigh the need to squeeze every drop of speed out of your system.

Databank Class

The Databank class itself is fairly simple; it's a wrapper around an std::map that allows you to manage the attributes within it easily:

template< typename type >
class Databank {
public:
    typedef std::map< std::string, type > container;
    typedef container::iterator iterator;
    iterator begin()     { return m_bank.begin(); }
    iterator end()       { return m_bank.end(); }

    bool Has( const std::string& p_name )
    void Set( const std::string& p_name, const type& p_val )
    type& Get( const std::string& p_name )
    void Add( const std::string& p_name, const type& p_val )
    void Del( const std::string& p_name )
    void Save( std::ostream& p_stream )
    void Load( std::istream& p_stream )
    void Clear()
    size_t size()

protected:
    container m_bank;
};

You can create databanks of any type you want, since this is a template class: for example, a Databank<int> , or Databank<float> . You can also iterate through databanks to see which variables they hold.

The second grouping of functions allows you to access and use the databank. To make databanks more "consistent," they throw exceptions when you try getting or changing attributes that don't exist, rather than accidentally creating them. Of course, to avoid throwing exceptions, you can first use the Has function to see if an attribute exists.

Since the functions simply wrap around the std::map functions and add exception throwing in the appropriate places, I'm not going to show you the code. You can find this class in /BetterMUD/entities/Attributes.h.

Using a Databank

Using a databank is pretty simple, as you can see from this example:

Databank<int> bank;
bank.Add( "health", 10 );
bank.Add( "strength", 20 );
int i = bank.Get( "health" );    // 10
i = bank.Get( "strength" );      // 20
bank.Del( "health" );
i = bank.Get( "health" );        // *THROWS EXCEPTION*
bank.Set( "strength", 30 );

A databank is string-based, so you can easily add any variables to it that you want. Every entity (except accounts) in the BetterMUD has a databank that you can use and access through the scripts, which will allow you almost limitless freedom to add variables to characters in the game.

Databanks and Streams

Databanks also have stream loading and saving functions, which makes it easy to load and save databanks to disk. Here's an example of the saving code:

void Save( std::ostream& p_stream ) {
    p_stream << "[DATABANK]\n";

    iterator itr = m_bank.begin();
    while( itr != m_bank.end() ) {
        p_stream << BasicLib::tostring( itr->first, 24 ) <<
            itr->second << "\n";
        ++itr;
    }
    p_stream << "[/DATABANK]\n";
}

This will create files that look like this:

[DATABANK]
health                  10
strength                20
hitpoints               100
[/DATABANK]

Nice, pretty, and readable. The call to the BasicLib::tostring makes sure there are 24 columns between the start of the attribute and the start of the variable. I could have used the setw function of streams to accomplish the same thing, but problems occur when compilers work in different ways ( strange for a "standard" library, right? C++ still has a long way to go.).

A databank is loaded in a similar way:

void Load( std::istream& p_stream ) {
    std::string temp;
    p_stream >> temp;       // extract "[DATABANK]"

    while( BasicLib::extract( p_stream, temp ) != "[/DATABANK]" ) {
        type t;
        p_stream >> t;
        Add( temp, t );
    }
}

The loop extracts each data tag using the BasicLib::extract function; each pass compares the data tag to see if it is the [/DATABANK] tag. If it is, the loop ends.

If the databank doesn't have an attribute extracted from the stream, it is automatically loaded. This is a particularly useful feature, because it allows you to load a databank from a file without manually adding all the attributes first.

[ LiB ]