Lesson 16.3 – Functions to use weapons and potions

Lesson Objectives

At the end of this lesson, you will know…

  • Nothing new. We’re just finishing the program, using the same things you learned in the previous lessons.

 

Now we’ll write the functions the player will use when fighting monsters.

We have two things they can do (besides click on one of the direction buttons, to run away from the battle): use a weapon on the monster or use a healing potion on themselves – if they have one in their inventory.

Here is the pseudo-code for the two functions:

Use a weapon function

  • Get the currently selected weapon from the cboWeapons ComboBox
  • Determine the amount of damage the player does to the monster
  • Apply the damage to the monster’s CurrentHitPoints
    • Display message
  • If the monster is dead (zero hit points remaining)
    • Display a victory message
    • Give player experience points for killing the monster
      • Display message
    • Give player gold for killing the monster
      • Display message
    • Get loot items from the monster
      • Display message for each loot item
      • Add item to player’s inventory
    • Refresh player data on UI
      • Gold and ExperiencePoints
      • Inventory list and ComboBoxes
    • “Move” player to current location
      • This will heal the player and create a new monster
  • If the monster is still alive
    • Determine the amount of damage the monster does to the player
    • Display message
    • Subtract damage from player’s CurrentHitPoints
      • Refresh player data in UI
    • If player is dead (zero hit points remaining)
      • Display message
      • Move player to “Home” location

 

Use a potion function

  • Get currently selected potion from cboPotions ComboBox
  • Add healing amount to player’s CurrentHitPoints
    • CurrentHitPoints cannot exceed player’s MaximumHitPoints
  • Remove the potion from the player’s inventory
  • Display message
  • Monster gets their turn to attack
    • Determine the amount of damage the monster does to the player
    • Display message
    • Subtract damage from player’s CurrentHitPoints
      • Refresh player data in UI
    • If player is dead (zero hit points remaining)
      • Display message
      • Move player to “Home” location
  • Refresh player data in UI

 

These are much simpler functions than the MoveTo() function.

We’ll be able to use some of the smaller functions we created during the refactoring lesson – for example, the AddItemToInventory() function, from the Player class, if the player defeats the monster and receives loot items.

 

Adding functions for monster battles

Step 1: Start Visual Studio Express 2013 for Desktop, and open the solution.

 

Step 2: Right-click on the SuperAdventure.cs form in the SuperAdventure project, to start working with the code for the UI.

To add the ability to use weapons and potions in combat, you can add in the code below, or replace the code for SuperAdventure.cs with the code here: https://gist.github.com/ScottLilly/b20787650f2ab2a78362

 

For the btnUseWeapon_Click function, add this:

        private void btnUseWeapon_Click(object sender, EventArgs e)
        {
            // Get the currently selected weapon from the cboWeapons ComboBox
            Weapon currentWeapon = (Weapon)cboWeapons.SelectedItem;

            // Determine the amount of damage to do to the monster
            int damageToMonster = RandomNumberGenerator.NumberBetween(currentWeapon.MinimumDamage, currentWeapon.MaximumDamage);

            // Apply the damage to the monster's CurrentHitPoints
            _currentMonster.CurrentHitPoints -= damageToMonster;

            // Display message
            rtbMessages.Text += "You hit the " + _currentMonster.Name + " for " + damageToMonster.ToString() + " points." + Environment.NewLine;

            // Check if the monster is dead
            if(_currentMonster.CurrentHitPoints <= 0)
            {
                // Monster is dead
                rtbMessages.Text += Environment.NewLine;
                rtbMessages.Text += "You defeated the " + _currentMonster.Name + Environment.NewLine;

                // Give player experience points for killing the monster
                _player.ExperiencePoints += _currentMonster.RewardExperiencePoints;
                rtbMessages.Text += "You receive " + _currentMonster.RewardExperiencePoints.ToString() + " experience points" + Environment.NewLine;

                // Give player gold for killing the monster 
                _player.Gold += _currentMonster.RewardGold;
                rtbMessages.Text += "You receive " + _currentMonster.RewardGold.ToString() + " gold" + Environment.NewLine;

                // Get random loot items from the monster
                List<InventoryItem> lootedItems = new List<InventoryItem>();

                // Add items to the lootedItems list, comparing a random number to the drop percentage
                foreach(LootItem lootItem in _currentMonster.LootTable)
                {
                    if(RandomNumberGenerator.NumberBetween(1, 100) <= lootItem.DropPercentage)
                    {
                        lootedItems.Add(new InventoryItem(lootItem.Details, 1));
                    }
                }

                // If no items were randomly selected, then add the default loot item(s).
                if(lootedItems.Count == 0)
                {
                    foreach(LootItem lootItem in _currentMonster.LootTable)
                    {
                        if(lootItem.IsDefaultItem)
                        {
                            lootedItems.Add(new InventoryItem(lootItem.Details, 1));
                        }
                    }
                }

                // Add the looted items to the player's inventory
                foreach(InventoryItem inventoryItem in lootedItems)
                {
                    _player.AddItemToInventory(inventoryItem.Details);

                    if(inventoryItem.Quantity == 1)
                    {
                        rtbMessages.Text += "You loot " + inventoryItem.Quantity.ToString() + " " + inventoryItem.Details.Name +Environment.NewLine;
                    }
                    else
                    {
                        rtbMessages.Text += "You loot " + inventoryItem.Quantity.ToString() + " " + inventoryItem.Details.NamePlural + Environment.NewLine;
                    }
                }

                // Refresh player information and inventory controls
                lblHitPoints.Text = _player.CurrentHitPoints.ToString();
                lblGold.Text = _player.Gold.ToString();
                lblExperience.Text = _player.ExperiencePoints.ToString();
                lblLevel.Text = _player.Level.ToString();

                UpdateInventoryListInUI();
                UpdateWeaponListInUI();
                UpdatePotionListInUI();

                // Add a blank line to the messages box, just for appearance.
                rtbMessages.Text += Environment.NewLine;

                // Move player to current location (to heal player and create a new monster to fight)
                MoveTo(_player.CurrentLocation);
            }
            else
            {
                // Monster is still alive

                // Determine the amount of damage the monster does to the player
                int damageToPlayer = RandomNumberGenerator.NumberBetween(0, _currentMonster.MaximumDamage);

                // Display message
                rtbMessages.Text += "The " + _currentMonster.Name + " did " + damageToPlayer.ToString() + " points of damage." + Environment.NewLine;

                // Subtract damage from player
                _player.CurrentHitPoints -= damageToPlayer;

                // Refresh player data in UI
                lblHitPoints.Text = _player.CurrentHitPoints.ToString();

                if(_player.CurrentHitPoints <= 0)
                {
                    // Display message
                    rtbMessages.Text += "The " + _currentMonster.Name + " killed you." + Environment.NewLine;

                    // Move player to "Home"
                    MoveTo(World.LocationByID(World.LOCATION_ID_HOME));
                }
            }
        }

 

Then, for the btnUsePotion_Click function, add this:

        private void btnUsePotion_Click(object sender, EventArgs e)
        {
            // Get the currently selected potion from the combobox
            HealingPotion potion = (HealingPotion)cboPotions.SelectedItem;

            // Add healing amount to the player's current hit points
            _player.CurrentHitPoints = (_player.CurrentHitPoints + potion.AmountToHeal);

            // CurrentHitPoints cannot exceed player's MaximumHitPoints
            if(_player.CurrentHitPoints > _player.MaximumHitPoints)
            {
                _player.CurrentHitPoints = _player.MaximumHitPoints;
            }

            // Remove the potion from the player's inventory
            foreach(InventoryItem ii in _player.Inventory)
            {
                if(ii.Details.ID == potion.ID)
                {
                    ii.Quantity--;
                    break;
                }
            }

            // Display message
            rtbMessages.Text += "You drink a " + potion.Name + Environment.NewLine;

            // Monster gets their turn to attack

            // Determine the amount of damage the monster does to the player
            int damageToPlayer = RandomNumberGenerator.NumberBetween(0, _currentMonster.MaximumDamage);

            // Display message
            rtbMessages.Text += "The " + _currentMonster.Name + " did " + damageToPlayer.ToString() + " points of damage." + Environment.NewLine;

            // Subtract damage from player
            _player.CurrentHitPoints -= damageToPlayer;

            if(_player.CurrentHitPoints <= 0)
            {
                // Display message
                rtbMessages.Text += "The " + _currentMonster.Name + " killed you." + Environment.NewLine;

                // Move player to "Home"
                MoveTo(World.LocationByID(World.LOCATION_ID_HOME));
            }

            // Refresh player data in UI
            lblHitPoints.Text = _player.CurrentHitPoints.ToString();
            UpdateInventoryListInUI();
            UpdatePotionListInUI();
        }

 

There isn’t really anything new in these two functions. Just more “if”s and “foreach”s to handle the player’s actions in battle.

 

Summary

Now you have a working game. The player can move around in the world, get quests, battle monsters, receive loot, and complete quests.

These new functions could use some refactoring, since they are long and do several things. I’ll leave that to you to figure out what refactoring you’d do.

 

Source code for this lesson

Source code on GitHub

Source code on Dropbox

 

 Next lesson: Lesson 17.1 – Running the game on another computer

Previous lesson: Lesson 16.2 – Refactoring the player movement function

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

245 thoughts on “Lesson 16.3 – Functions to use weapons and potions

  1. Hi Scott,

    First, thank you for the tutorial. It has been very helpful for me.

    I’ve followed the lessons up to this point and the game works otherwise, but the monsters don’t spawn at the locations. When debugging I noticed that the “newLocation.MonsterLivingHere” always stays at “null” at every location and I cannot figure out why. I’ve copied your code to World.cs, Location.cs and SuperAdventure.cs while troubleshooting, but to no avail. I’d appreciate any tips or help you can give me. Thanks.

    By the way, I use Visual Studio 2019, if that matters.

    Br,
    Ilkka

  2. Hello Scott. Firstly, I wanted to say thank you for making such a great tutorial for C#, I’m actually very new to OOP and coding in general, and I’m getting use to how everything works (Though I still have a long way to go.)

    But I have a problem in regards to this project. I’ve gotten to 16.3 with no errors, and seemingly everything is running just fine. However, there is one problem, no text is appearing in the rich text boxes! The UI and everything else seems to be fine, it’s just that the text boxes are completely blank. Did I mistakenly skim over a section without seeing something, or is it just a rookie mistake?

    I’m looking forward to hearing back to you. I got this far and don’t want to give up just yet 🙂

  3. Hello Scott, I got a problem with my Quests. I dont get the RewardGold/RewardPoints from the quests. When I fight against monsters, everything is working fine.. I tried to find a solution by myself, but I failed…

    Would be cool if you can help me out there 🙂

      1. Ok I think I found the problem. Its not updating the counter. If I kill a monster after that, the gold+experience gets updated.. Never even thought of that… xD

    1. Hi Isaac,

      I got your code and it looks like some of the classes in the Engine project are not set to “public”. For example, line 9 of Item.cs needs to be “public class Item”. Check that in HealingPotion.cs, Item.cs, Location.cs, Monster.cs, Quest.cs, RandomNumberGenerator.cs, and Weapon.cs.

      Let me know if you have any questions, or if that doesn’t eliminate the errors.

  4. Line 148 throws error since it requires two parameters passed into it.
    _player.Quests.Add(new PlayerQuest(newLocation.QuestAvailableHere));

    Could you please ensure it is right?

  5. When I try to run the game in debug mode, it throws a warning saying that in PlayerQuest.cs the variable questAvailableHere is never used.

    private Quest questAvailableHere;

  6. Hi Scott, thanks for all of this … I’ve picked up a Games Design and Programming class and this project has helped speed me up in C# (I haven’t done any C-based stuff in years).

    Most things work fine, but I’m getting an error when I kill a monster and loot is generated:
    // Add the looted items to the player’s inventory
    foreach (InventoryItem inventoryItem in lootedItems)
    {
    _player.AddItemToInventory(inventoryItem.Details);

    The error is:
    An unhandled exception of type ‘System.InvalidOperationException’ occurred in mscorlib.dll
    Additional information: Collection was modified; enumeration operation may not execute.

    I’m sure it’s something small but I can’t see it … any ideas?

    1. You’re welcome, Darren!

      Can you upload your solution (including the directories under it, and all the files in those directories) to GitHub or Dropbox, so I can look at it? I know what the error is, but I need to see your code to see exactly why it’s happening for you.

  7. Hi Scott,
    i wanted to ask this this before but…
    all people here in comment are writing how they are playing the game. How is that going?
    i have everything like you have but i cant play the screen show me the textboxes an the other things
    it sounds very stupid i know but can you help me?

  8. Added this to keep the text scrolling properly
    private void rtbMessages_TextChanged(object sender, EventArgs e)
    {
    rtbMessages.SelectionStart = rtbMessages.Text.Length;
    // scroll it automatically
    rtbMessages.ScrollToCaret();
    }

  9. Hey there Scott i was following the guide and got thrown an exception a the very end of this tutorial and im really not sure what to do. I (think i) uploaded the solution to github, here is the link in case you find yourself finding the time to look into this for me, thank you. https://github.com/HermansGrave/SuperAdventure.git

    The exception states,
    “System.TypeInitializationException: ‘The type initializer for ‘Engine.World’ threw an exception.’
    Inner Exception
    NullReferenceException: Object Reference not set to an instance of an object.

    This exception was originally thrown at this call stack:
    Engine.World.PopulateQuests() in World.cs
    Engine.World.World() in World.cs”

    1. Hi Joseph,

      Check line 24 of the Quest.cs class and compare it with the code in Lesson 10.1. You create a list of QuestCompletionItems, but it isn’t being populated into the QuestCompletionItems property. This cause a problem in the static World class when the Quest objects are being populated. The QuestCompletionItems property must have an empty list, in order to add items. But, right now, it’s null – which causes the error.

      Let me know if you have any questions, or if that doesn’t help fix the error.

Leave a Reply

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