Lesson 25.1 – Select a random monster at a location

A reader asked how to have several different monsters that could be at a location – and randomly select one when the player moves to that location.

This is a little complex, so I created this lesson, to show how.

 

Planning the change

Currently, the Location class has a MonsterLivingHere property, which holds a single Monster object (or nothing, if there isn’t a monster at that location).

We use that property in the World class, to set the monster at a location. We also use it in the UI code, to detect if there is a monster at the location – so we can hide, or show, the combat buttons. And, we use it to instantiate a new monster to fight – using the NewInstanceOfMonsterLivingHere function.

 

To allow different possible monsters, we need to change the PopulateLocation function in the World class, to let us add multiple monsters to a list, along with the percentage chance for them to appear at the location.

Then, in the UI, instead of checking that single Monster property, we need to check if there are any monsters in the Location’s monster list.

Finally, we need to change the NewInstanceOfMonsterLivingHere function to get a random monster from the list, when it creates the Monster object for the Player to fight.

 

Making the changes

Step 1: Change Location.cs

 

We add a new private variable (_monstersAtLocation) that is a SortedList.

A SortedList is like a List, but lets you store two values per entry. These can be any types of values: strings, integers, your custom classes, etc.

For this SortedList, we will want to hold two integer values.

The first value is the “key”. You cannot have two items with the same key. The keys will be populated with the monster ID. So, we cannot add an entry to the SortedList that has the same key value (Monster ID) as an entry already in the SortedList.

The second value is the “value”. This is where we will store the percentage chance that this monster appears, when the Player moves to the Location.

 

To populate the SortedList, we add the “AddMonster” function.

This checks if the key (Monster ID) already exists in the SortedList, using the ContainsKey function. If the SortedList already has an entry with that key, it will replace the value for that entry with the new value. If there is no entry with the passed key, the function will add this entry to the variable.

 

Next, we add a new HasAMonster property, that checks if there are any entries in the _monstersAtLocation variable.

Before, when we wanted to check if there was a monster at a location, we used “MonsterLivingHere != null”. Since we’re replacing the MonsterLivingHere property with the new logic, we need a new way to check for monsters at a location.

 

The third step is to change the NewInstanceOfMonsterLivingHere function. Now, we need to randomly select one of the possible monsters, from the _monstersAtLocation variable, and create an instance of it.

This code sums all the percentages, and selects a random number that is less than, or equal to, that sum. This way, the function can handle situations when the total percentages do not equal 100.

Then, it loops through the list of possible monsters – adding the monster’s percentage chance of appearing to a running total variable.

If the random number is less than the running total, the function returns the monster. If not, it loops to the next monster in the list.

 

Finally, we can remove the MonsterLivingHere property – and its parameter in the constructor.

 

 

Step 2: Change World.cs

 

We were setting the location’s MonsterLivingHere property in the PopulateMonsters function. Now, we need to change it to use the new AddMonster function.

To set the monsters at a location, pass in the monster ID and the percentage chance it will be selected.

If you wanted rats to appear in the farmersField 20% of the time, and snakes the other 80%, your code could look like this:

 

farmersField.AddMonster(MONSTER_ID_RAT, 20);

farmersField.AddMonster(MONSTER_ID_SNAKE, 80);

 

STEP 3: Update the code that was using the MonsterLivingHere property.

 

There were three other classes that used the MonsterLivingHere property. We need to change them, to use the new property.

 

Player.cs

Inside the SetTheCurrentMonsterForTheCurrentLocation function (line 385), change this line:

To this:

 

SuperAdventure.cs (in the Windows Form UI project)

Inside the PlayerOnProperyChanged function (line 150), change this:

To this:

 

Program.cs (in the SuperAdventureConsole project)

Inside the AttackMonster function (on line 229), change this:

To this:

 

If you’ve modified the program, with your own changes, do a search for any other code that uses “MonsterLivingHere”, and replace them with the new property, or function.

 

Summary

When you modify an existing program, you need to find all the places where the current code is used. Then, plan how to get them to all use the new code.

Sometimes, this can be difficult – especially in larger programs. However, the time you spend in preparation will almost always save you more time, once you begin making the changes.

 

 

Source code for this lesson

From GitHub: source code on GitHub

From Dropbox:  source code on Dropbox

 

Previous lesson: Lesson 24.1 – Make the SuperAdventure source code easier to understand and modify

All lessons: Learn C# by Building a Simple RPG Index

 

16 thoughts on “Lesson 25.1 – Select a random monster at a location

  1. This is great stuff!! I am thinking about adding a map you can open where it will populate as you travel throught the world.. And also a “delete save” function. So you can start over.

    I really have enjoyed these! 😀

    Thank you so much for the effort.

    1. You’re welcome!

      I’ve seen a couple other people modify the game to show a map. But, I haven’t seen one where it only shows the locations the player has visited. That would be a cool feature to add!

  2. Hi!
    I Like your tutorial. It is Great.
    Here is my thoughts to my future improvments:
    I want to add a NPC class, and add them to locations.
    NPC can give you a quests.
    I want to add something like dialogue system, through that system player can take a quest from NPC.

    P.S. sorry for my English, I’m from Russia =)

    1. Hello Den,

      Thank you. I’m glad you like the tutorial. I will think about how to add a dialogue system for an NPC. However, I won’t be able to work on it until April (I have a big project I need to finish this month). If you want to start looking for ideas before then, you might look for code about “state machines”.

      P.S. Your English is good. It is better than my Russian. 🙂

  3. Thanks! I’ll try to understand and realize state machine.
    I have another one question.
    My game grows in size, the number of items, monsters, locations increases. So I thought, is it correct to keep the identifiers of all objects in constants? How is it stored in big games? After all, as the content of the game increases, the number of constants will grow and it will be easy to get confused in them.

    And second question.
    I want to move all my world description in the xml, and add this xml file to resources.
    How do you thnk, it is good idea, or not?

    1. You’re welcome! I’ll answer your second question first, because it is easier. 🙂

      Yes, using an XML file (or database) would be a good way to build a larger game – more items, monster, locations, etc. You can use Visual Studio to edit the XML file. It can help ensure the XML does not have errors.

      For your first question, you do need to have a unique identifier for each monster/item/quest/etc. The large games also use IDs the same way. If you store the World data in an XML file, you will still need to use an ID. However, you could remove the constants from the World class. We only need the IDs in the World class to help us connect things – like which quest to have at a location, where we use “alchemistHut.QuestAvailableHere = QuestByID(QUEST_ID_CLEAR_ALCHEMIST_GARDEN);”. In the World XML file, you could manage that the same way the Player XML file manages its inventory and quests. Each Location node could have a “child” MonstersHere node, which has “child” nodes for each Monster (with its ID). This would look like the /Player/InventoryItems/InventoryItem nodes in the Player XML.

      Do you see how that could work? If not, please tell me.

      1. I was thinking in this direction. I understand how this should work, so I just confirmed my guesses =).

        Sorry for bothering =)

  4. Hi Scott, I get this error after this lesson, and I can’t solve it :/
    Error CS1061 ‘Monster’ does not contain a definition for ‘NewInstanceOfMonster’ and no extension method ‘NewInstanceOfMonster’ accepting a first argument of type ‘Monster’ could be found (are you missing a using directive or an assembly reference?)
    Can you help me?

    1. Hi Daniel,

      Check that your Monster class has this change from Lesson 24.1. It adds the “NewInstanceOfMonster()” function that creates a new instance of the Monster, with all its hit points, and new (random) loot. If that is already in the class, make sure it is set to “internal”, so other classes in the project can use it.

      If that does not fix the error, please let me know.

  5. Thanks, i did it and then the program runs, but i didn’t do the 24.1 completely before 25.1 so the battle button appear when im in a no monster area, and also no monster spawn where they should… So i reverted back to an older version.
    I don’t want to copy&paste SuperAdventure code because a changed a bit. I find it a little difficult to implement 24.1 and 25.1 (code has changed a lot in two lessons) without having to ruin the whole thing.
    Can you do a tutorial about show an item sprite before the item name in the inventory and vendor inventory?
    Sorry for my ban english!

  6. Your tutorial are grate. Thanks you so much. You opened my way to become a good programer. I think to start to learn how to use unity now. Maybe in one day you will play my own game.
    Good luck in all what you do.
    And sorry if my english is bad.

Leave a Reply

Your email address will not be published. Required fields are marked *