Game Additions

[ LiB ]

The two main additions to this version of the MUD are enemies and the game loop, but additions have been made to the old versions as well. You've already seen the EnemyAttack and PlayerKilled functions that were added to the Game class. Along with those additions, there are two more combat functions, and two new commands ("attack" and "reload enemies") added to the Game class. These constitute a bunch of tiny changes and additions.

Other Combat Functions

The other two combat functions are PlayerAttack (which is executed when a player attacks an enemy) and EnemyKilled (which runs when an enemy is killed ).

The PlayerAttack function is similar to EnemyAttack , which is a result of the design of the game. Since players and enemies are two completely different entities within the game, you need specialized functions to perform attacks in both ways.

NOTE

Having two functions that do essen tially the same thing is usually a sign that you're doing something wrong. A more flexible MUD would have enemies and players be part of the same entity type, or even inheriting from a common base class. If you decide to change the way combat is performed in SimpleMUD, you'll end up needing to change both functions, which can be a major source of errors. BetterMUD ends up treating players and enemies as the same entity type in order to solve this problem.

Attacking Enemies

The first part of the PlayerAttack function (where the function finds the target) is the only thing that differs from the EnemyAttack function. In the enemy version, the enemy picks a random player to attack, but whenever a player attacks an enemy, he has usually typed in the name of the enemy (for example attack goblin ), so the function needs to take that text and figure out which enemy the player is trying to attack:

 void Game::PlayerAttack( const string& p_enemy ) {     Player& p = *m_player;     sint64 now = Game::GetTimer().GetMS();     // check if player can attack yet     if( now < p.NextAttackTime() ) {         p.SendString( red + bold + "You can't attack yet!" );         return;     }     // find the enemy, and if it isn't found, tell player.     enemy ptr = p.CurrentRoom()->FindEnemy( p_enemy );     if( ptr == 0 ) {         p.SendString( red + bold + "You don't see that here!" );         return;     } 

Remember that since this function is within the Game handler, it knows which player is attacking ( m_player ). The parameter for the function contains the name of the enemy the player wishes to attack.

The rest of the function is essentially the same; it calculates the damage, swing time, whether the enemy is hit, and so on. When PlayerAttack detects that an enemy has died, it calls the EnemyKilled function.

Killing Enemies

The process that occurs when an enemy is killed is much different from the process that occurs when players die. The most obvious difference is that enemies don't have to be immediately respawned, since the game loop already takes care of that.

Another difference is that the function needs to go through the enemy's loot-list and figure out what to drop, as well as how much money to drop, and how much experience to add to the player who killed it.

Here's the first part of the code, which notifies a room about an enemy being killed and drops its money:

 void Game::EnemyKilled( enemy p_enemy, player p_player ) {     Enemy& e = *p_enemy;     SendRoom( cyan + bold + e.Name() + " has died!", e.CurrentRoom() );     // drop the money     money m = BasicLib::RandomInt( e.MoneyMin(), e.MoneyMax() );     if( m > 0 ) {         e.CurrentRoom()->Money() += m;         SendRoom( cyan + "$" + tostring( m ) +                 " drops to the ground.", e.CurrentRoom() );     } 

The parameters of the function are the instance ID (not template ID) of the enemy who has died, and the player ID of the player who killed him.

The code uses a random number generator to generate the amount of money that has dropped, and then drops it, and tells everyone about the new fortune on the floor.

The next piece of code drops all the loot:

 std::list<loot>::iterator itr = e.LootList().begin();     while( itr != e.LootList().end() ) {         if( BasicLib::RandomInt( 0, 99 ) < itr->second ) {             e.CurrentRoom()->AddItem( itr->first );             SendRoom( cyan + (itr->first)->Name() +                       " drops to the ground.", e.CurrentRoom() );         }         ++itr;     } 

The code loops through every entry in the loot list and calculates if the item needs to be dropped. This is done by generating a random number from 0 to 99, and checking to see if the loot's percent chance is less than this number. For example, a loot entry with a probability of 20 would be dropped whenever the numbers 019 are generated (20 numbers out of 100, or 20%), and an entry with a probability of 0 would never be dropped (since the generator can never generate numbers lower than 0). Entries with a probability of 100 will always be dropped (because the generator always generates numbers below 100).

The last piece of code rewards the slayer and removes the enemy from the game:

 Player& p = *p_player;     p.Experience() += e.Experience();     p.SendString( cyan + bold + "You gain " +                   tostring( e.Experience() ) + " experience." );     EnemyDatabase::Delete( p_enemy ); } 

That's all there is to combat in SimpleMUD.

New Game Commands

The Game class must be augmented to handle the two new commands introduced in this version of the MUD: attack and reload enemies . As usual, these commands are added to the large Game::Handle function found within /SimpleMUD/Game.cpp.

Here's the first one:

 if( firstword == "attack"  firstword == "a" ) {     PlayerAttack( RemoveWord( p_data, 0 ) );     return; } 

This simply calls the PlayerAttack function you saw earlier with the word attack removed from the string. (For example, attack goblin would pass goblin into the function.) You can also use the letter a as a shortcut for the whole word, as in a goblin .

Code for reloading enemies is added in the middle of the reload command you read about in the two previous chapters. The new code block is in bold and the code blocks above and below it are for reference.

 else if( db == "stores" ) {     StoreDatabase::Load();     p.SendString( bold + cyan + "Store Database Reloaded!" ); }  else if( db == "enemies" ) {   EnemyTemplateDatabase::Load();   p.SendString( bold + cyan + "Enemy Database Reloaded!" );   }  else {     p.SendString( bold + red + "Invalid Database Name" ); } return; 

Essentially, the code tells the enemy template database to load all its templates. The instance database cannot be reloaded for the same reasons I gave in the previous chapter about not being able to reload room data, only room templates.

Additional Code Changes

Many changes still need to be made to the code to support the addition of enemies into the SimpleMUD.

Database Pointer Placeholders

First and foremost, these two lines need to be removed from Room.h:

 typedef entityid enemytemplate;     // REMOVE THIS LATER typedef entityid enemy;             // REMOVE THIS LATER 

This is because those two classes are now defined in DatabasePointer.h, and the lines would cause a compiler error.

New Room Functions

The next step is to add three functions to the Room class:

 enemy FindEnemy( const string& p_enemy ); void AddEnemy( enemy p_enemy ); void RemoveEnemy( enemy p_enemy ); 

You saw the code presented earlier in this chapter using these three functions to find, add, and remove enemies in rooms. These three functions are similar to the FindItem , AddItem , and RemoveItem functions, so I'm not going to show you the code here.

NOTE

The AddEnemy function's logic is slightly different from AddItem . The item function automatically deletes items once it reaches the limit of 32 items, as you saw in Chapter 9 , but the enemy version of the function doesn't do this. Instead, the maxi mum number of enemies in a room is determined by the MaxEnemies function of the Room class. Enemies are never deleted, because the spawning function makes sure the maximum is never exceeded.

Printing Enemies

The final addition is within the Game::Printroom function, where code is added to print a list of all the enemies within a room. The code for printing enemies is added after the code that prints players, but it's really not that interesting, so I'll show you the results of the code instead:

 Alley You're in a dark alley, where shadows obscure your view and hide dangerous things... exits: NORTH  SOUTH People: mithrandir Enemies: Rabid Monkey, Rabid Monkey, Rabid Monkey 

Now players always know what enemies are in a room with them.

Main Module Changes

The main module for this version of SimpleMUD is contained within the Demo10-01.cpp file, and it is much different from the versions you saw in Chapters 8 and 9.

The main module in the two previous versions explicitly called the loading and saving functions of the various databases, but you don't need to do that anymore. The GameLoop class has functions to do that for you.

So instead of manually loading all the databases in the main module, you'll declare a GameLoop object instead. Later on, in the actual while-loop, the GameLoop objects' Loop function is called. Here's the main part of the main function, with the important sections bolded:

 try {  GameLoop gameloop;  ListeningManager<Telnet, Logon> lm;     ConnectionManager<Telnet, Logon> cm( 128, 60, 65536 );     lm.SetConnectionManager( &cm );     lm.AddPort( 5100 );     while( Game::Running() ) {         lm.Listen();         cm.Manage();  gameloop.Loop();  ThreadLib::YieldThread();     }  }  

The loop object is created at the top of the previous code segment. The loop object automatically loads all databases when it is created, so you don't have to do that here anymore.

The next bolded line shows the function that calls GameLoop::Loop inside the main while-loop. This ensures that the loop object performs all the necessary timer-based actions (enemy attacking, spawning, player health regeneration, and database saving).

The final bolded element is the final bracket on the last line of the code. When your code exits this try-block, the GameLoop object goes out of scope, which means that its destructor is called. A GameLoop 's destructor automatically saves all of the databases that need to be saved, so there's no need to manually call those earlier.

[ LiB ]


MUD Game Programming
MUD Game Programming (Premier Press Game Development)
ISBN: 1592000908
EAN: 2147483647
Year: 2003
Pages: 147
Authors: Ron Penton

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net