Lesson 26.2 – Hiding Unvisited Locations on the World Map

Now that we have a map, it would be nice to only show the images for the locations the player has visited. That is what we’ll add in this lesson.

 

NOTE: In the last lesson, there wasn’t an image for the Bridge location. That’s because I used the images from the WPF version of these lessons, and there is no Bridge location in that game.

So, if you finished Lesson 26.1, and you don’t have a Bridge image (and you don’t have six columns of PictureBox controls), please go back to Lesson 26.1 and make the changes to add the missing column. This will require downloading the location images again (to get the Bridge image), and changing the WorldMap form (to add the new column and display the Bridge image).

 

STEP 1: Add FogLocation.png to SuperAdventure\Images

After adding it, set its properties to:

Build Action: Embedded Resource

Copy to Output Directory: Do not copy

 

Right-click and select “Save as”, to download

 

 

Step 2: Edit Engine\Player.cs

We’re going to store the ID of every location the player visits. We’ll save the IDs in a new List property named LocationsVisited (line 69).

Because this is a List property, we need to initialize it, otherwise it will be null, instead of an empty List. We’ll do that in the constructor (on line 80), where we initialize the other list properties.

Now, when the player moves to a new location, we need to add its ID to the property – if it hasn’t already been added. We do that inside the MoveTo function, on lines 167 to 170. If the LocationsVisited property does not already contain the ID of the location, we add it to the List.

 

Player.cs

 

Step 3: Edit SuperAdventure\SuperAdventure.cs and SuperAdventure\WorldMap.cs

In order to display the correct image for a location (the fog, or the location’s image), the WorldMap form needs the current player object, to know which locations the player has visited. So, we need to pass it from the SuperAdventure form, into the WorldMap form – like we do with the TradingScreen form.

In WorldMap.cs, we need to add a Player parameter to the constructor (line 13).

In SuperAdventure.cs, we pass the current player when we instantiate the WorldMap form (line 225).

 

Now, we can hide the unvisited locations by displaying the FogLocation in the PictureBox for any locations whose IDs are not in the player object’s LocationsVisited list.

I’ve done that by using the ternary operator inside the calls to SetImage (lines 17 through 25). If LocationVisited contains the location’s ID, we pass the name of the location’s image file. If the ID is not in LocationsVisited, we pass “FogLocation”.

 

WorldMap.cs

 

SuperAdventure.cs

 

Step 4: Edit Engine\Player.cs

We want to remember the player’s LocationsVisited values between game sessions. So, we need to update the code that saves the player’s data to the saved game file – and the code that creates the player object from that file.

In the ToXmlString() function, we’ll add a new section that creates nodes with the ID values in LocationsVisited (lines 349 through 361). This is like the code to save the InventoryItems and PlayerQuests.

We create a LocationsVisited node, with a child node named LocationVisited, to hold the location ID.

In the CreatePlayerFromXmlString() function we add code to read those values from the saved game file (lines 116 through 121).

 

Player.cs (with changes to save/read LocationsVisited from saved game file)

 

Step 5: Edit Engine\PlayerDataMapper.cs

We also need to save the LocationsVisited values to the database, and read them when loading a saved game from the database – if you are using a database to save the game data.

To save the location IDs, we’ll create a new table named LocationVisited. It will only have a single column “ID”, whose datatype is “int”, and does not all nulls. The script to create it is below.

 

 

Next, we need to update PlayerDataMapper to save the values into this table, and read the values from it.

The code to do this is like the code for adding and reading the values for the InventoryItems and PlayerQuests.

 

We save the Location IDs to the table in the SaveToDatabase() function, at lines 286 through 307.

The code to read from this table is in the CreateFromDatabase() frunction, at lines 111 through 131.

 

NOTE: I noticed a bug with the readers not closing. So, inside each “using” block of code in CreateFromDatabase, I’ve added a “reader.Close();”. These are on lines 58, 85, 108, and 130.

 

PlayerDataMapper.cs

 

Step 5: Test the game.

Now, as the player moves to new locations, the map will display more images – instead of the “fog” image for unvisited locations. The map should start to look like this (for example):

 

 

Summary

This uses hard-coded values for placing the images in the PictureBox, which isn’t the best way to create a map. This would be much more flexible if we used X and Y coordinates for the locations. Then, we could do things like having the map always centered on the player’s current location, and showing a 5 x 5 (or larger) grid of the surrounding locations.

If you follow the “Build a C#/WPF RPG” lessons, that is how we are building that world.

 

Source code for this lesson

Source code on GitHub

Source code on Dropbox

 

Previous lesson: Lesson 26.1 Displaying a World Map

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

21 thoughts on “Lesson 26.2 – Hiding Unvisited Locations on the World Map

  1. Hi!

    The same problem, that causes the Quest bug, adds the last saved location to the empty LocationsVisited list, and that causes duplicated rows in the LocationVisited data table after you start the program multiple times.

    I think Lesson 99.1 Bugfix will solve this problem.

    1. Correct, it should solve that problem. If anyone has duplicated locations in their data, they should probably delete the second (or more) instances of the locations. It might also be nice to create an AddLocationoLocationsVisited function (like we do for adding items to the player’s inventory) that would prevent adding a duplicate location. Although, it’s always best if we have code the doesn’t let the data ever get into a bad condition.

  2. Hi Scott! Thank you very much again i need help :C
    I managed to do everything and i was happy, then i got the final step of localisation so other programmers would understand the code in my contry, and i started… I was happy that everything worked and went away from the project for a while and then i realised that the project is not saving the player.. only creating new one every time when a user tries to play. I tried to pin the problem down but everything was in vein, the program doesn’t show any sign of mistakes but still doesn’t save the player. So i just tried to return all the names of the functions and methods and try again, there were no mistakes but still nothing new.. Could you pls help me with my final step of finishing the game. If you can’t spot the problem that’s all right i will just delete this function of saving from the game, i’ll be happy to any help!

    https://www.dropbox.com/s/dyf5bhm9tpkkmrn/ForgottenLand2.rar?dl=0

    1. I think I found the problem. In ForgottenLand.cs, starting on line 89, the “else if” code is probably not running the way you want. There are lines after the “else if”, but before the {} code block. When code is like that, the compiler pretends there is a pair of curly braces around the next one line – and not any lines after it.

      There are some notes here: https://gist.github.com/ScottLilly/8516a40ec115b3c512da096e2cf8b8ea

      Because there is not an “if” or “else if” before line 8 (in Before.cs), that code always runs. So, the program always creates a new player.

      If you want to see what is happening, you can use the debugger. Here is a lesson on how to use the Visual Studio debugger. You can set a breakpoint on line 89 and see what is happening when you run the program.

      1. Thank you! This is unbelievable how only one line can stop all the work of the application. it is difficult to imagine how people can work with multiple projects with over 1000 lines in it.

  3. I could not run the SuperAdventure because I was hitting an error about the Quests containing more than one entry for the same quest, ID = 1.

    I fixed the problem in my copy of the game by doing the following in the Player.cs file.

    Above the PlayerDoesNotHaveThisQuest method I added the following method:

    Then I changed the GiveQuestToPlayer method by adding an if statement at the top:

    There may be a better way of fixing this error, but this particular way worked for me.

    1. Hi Ferlin,

      There was a bug if the player exited the game at a location with a quest. When they restart the game, it gives the player the quest from their saved Quests list, and tries to give the quest again since they are at the location. Your fix will prevent the game from crashing – or, you can use the changes in lesson 99.1.

  4. I was looking back on the site and was surprised to see all the changes to the application and some integration with the WPF project! I was going to do some expansion and add images to the original program only to find that it has already been done! Great job on this Scott. It has been a real help to a lot of folks.

    1. Thanks Stan! This started out as a little project in my spare time, but has grown larger than I ever thought it would. It always great to hear from people who have learned something from the lessons.

      I just started a new series this week to refactor the SuperAdventure code into higher-quality code that will be easier to work with and modify.

  5. Hi Scott,

    Thank you for taking your time to create this tutorial, I have found it extremely helpful! However there are a few things I am unsure of, how would I go abou highlighting the current position on the map form? And how would I go about making quests repeatable? I’ve made several changes to the game, including adding item weighhs and inventory max weight, but I can’t seem to update the players current weight after adding or removing quest items, any suggestions would be appreciated. Thanks in advance!

    1. Hello Ryan,

      You’re welcome! Here is some code you can use to highlight the player’s current location in the world map: https://gist.github.com/ScottLilly/f02d10322a8184f2424a2e601972786c. I did a little cleanup of this class, but the important part is in lines 41-51.

      If you want all quests to be repeatable, you would need to change the MoveTo code that gives the player the quests (line 183 of Player.cs). Currently, it gives the player the quest if they do not already have it. You could change that to “if the player does not have the quest, or if they do have it, but it’s completed”. Or, when the player completes a quest, you could remove the PlayerQuest object from the Player.Quests property. Or, you could modify the Quest class to have a boolean property “IsRepeatable”, if you only want some quests to be repeatable. Then, change your code around line 183 (of Player.cs) to give the player the quest “if the player does not have the quest, or if they have the quest, it’s completed, and it’s repeatable)”.

      For the item weights, I would create a “CalculateWeight” function that is called at the end of “AddItemToInventory” and “RemoveItemFromInventory”. Make sure your Weight property is calling OnPropertyChanged, so the UI knows it needs to refresh the screen, and that your SUperAdventure constructor has a DataBindings.Add for the label displaying the weight value.

      Let me know if you have any questions about those suggestions.

      1. Wow, thanks for such a quick response! Your suggestions are certainly valuable, I have a pretty basic understanding of the OOP paradigm, I understand inheritance, polymorphism and such but I struggle to apply these concepts to real world problems, it amazes me how I didn’t even consider the suggestions you made of my own accord, now you’ve suggested them it seems very simplistic. I do however have another question, I’m not familiar with LINQ at all, I’ve never encountered it before your tutorial and I was wondering if I could utilise it for filtering the inventory list when displaying the vendors trading form to filter out quest items, now would I need to make a new list and bind that to the data grid view or can I simply filter the existing inventory list? The code I have right now doesn’t display quest items, but it also removes the item from the players inventory, thanks again Scott!

        1. You’re welcome. The more programs you work on, the more you’ll build up a mental “library” of how to solve problems and make changes. If you haven’t studied “design patterns“, those are some common solutions to common problems that are good to learn.

          You could use LINQ to create a filtered list of inventory objects. That’s what we do with Player.Weapons and Player.Potions. So, you could create a “List SellableItems” property. Just remember these “derived properties” don’t automatically notify the UI of changes. So, when you change the base/underlying property, you’ll need to manually raise a PropertyChanged notification for your derived property. We do this when we call RaiseInventoryChangedEvent from AddItemToInventory and RemoveItemFromInventory – to notify the UI that the derived “Weapons” and “Potions” property has changed, and needs to be refreshed in the UI.

          Or, you could apply a filter to the DataGrid.DataSource, like this: https://stackoverflow.com/questions/21845016/how-to-filter-datagridview-in-c-sharp-win-forms.

          1. Hey Scott,

            I’ve made several changes since my last post. Thanks to your help I’ve managed to resolve the item weights being incremented or decremented whenever a quest item is awarded or removed respectively, I kind of followed your suggestion and rather than creating a new function, I modified the existing function UpdatePlayerStats() which calculates the players inventory weight by multiplying the item weight by quantity for each item.

            I’ve also managed to create repeatable quests which prompted another issue of duplicate quests, however I managed to resolve this issue by modifying the MarkQuestAsComplete(Quest quest).

            I have since ran into a number of issues where I would greatly appreciate your assistance. First of all I’ve read over multiple comments on pretty much all the lesson pages to see if I can find an answer, before taking up any more of your time. You suggested to someone on how to save and load the vendors inventory to XML, I’ve managed to get my game to successfully save the vendors inventory, however I’m unsure as to how to load the data, you mentioned creating a function in the TradingScreen constructor for reading the data and then passing the data to the Vendor class, this is where I’m stumped.

            I was also wondering how I would go about changing the XP needed to reach the next level, determined by the players current level and difficulty setting, I think I know how to do this but it would require modifying how the players level is calculated, I’ve also encountered several posts on here that refer to the same issue, however my maximum player level is 100 so if else statements seem inadequate. I have messed around with this loads but have failed to come up with anything even remotely close to what I’m trying to achieve.

            Additionally I’d like to remove all the players inventory items besides unsellable items and one weapon whenever the player dies(A weapon that the user can choose if multiple weapons are present). I understand you have priorities, I am going to try and solve all these issues by myself in the meantime, but if you could point me in the right direction that would be extremely helpful, thanks a bunch!

          2. Hi Ryan,

            I saw your other comment that you got some of the new features working. That’s great!

            For the trader inventory, it’s probably simplest to load the trader’s inventory in the World.PopulateLocations() function. However, because the World class is static, it might be difficult to catch any errors there. If you do have any problems, set a debug breakpoint at the beginning of PopulateLocations() and step through the code (using F10) to see where the error happens.

            For the experience points needed for each level, you would probably want to create a mathematical equation that gradually increases the amount of XP needed for each level. This is often done with an exponent, like in these examples: http://howtomakeanrpg.com/a/how-to-make-an-rpg-levels.html

  6. Okay,

    Update:

    After a lot of debugging I’ve managed to implement the removal of all items besides any unsellable items and the current weapon when the player dies. I’ve also managed to implement the fleeing message whenever a player moves to a new location and they’ve been attacked but haven’t killed the monster.

  7. Hello again Scott, I know it’s been a while, I’ve been moving house myself. Since my last post I’ve added several items into the game, including food and armour. I decided to add a tab control with each tab displaying different items, for instance a tab for all items, weapons, armour, food etc… and each tab has a datagridview, this all works fine, updates and everything however I don’t know how to display the quantity for items such as food or weapons since they are bound to lists of the respective object, not inventory items, each of the classes has properties which I am displaying in the datagridview therefore filtering the inventory for each item type doesnt appear to be an adequate solution. I even added the ability to move up or down within a location so for example your home has a basement and first floor, again this works as expected however I’d like to display both a world map and a local map, whenever I create a new world map object it is populated with the same images, I’m not sure how to separate and differentiate between the two. Additionally I’ve added the ability for the player to add a chest to their current location, this value is then saved in the players XML file, and when the game is relaunched these values persist until the player moves location and the values are overwritten by the world class, I’m not sure how I would go about this issue, could storing the Boolean chest value in visited locations be a solution? My computer isn’t assembled right now so I can’t actually check to see if this would work or not. I am sorry for the long list of problems but another issue I’ve been having is, I’ve changed the datagridviews button text when certain conditions are met, this works as expected but then the logic I have is determined by the buttons text value which always seems to return the item ID regardless of the texts value, I know they are linked in order for the logic to reference the item by ID but I don’t understand why this is happening. Any insight would be receieved with the utmost appreciation, thank you!

    1. Hi Ryan,

      I hope you’re settled in to your new house. Moving always drains my energy for a couple weeks. It sounds like you’ve done a huge amount of expansion of the game!

      One way to show quantities in the inventory is in the WPF lessons (10.2 and 10.4). We have a GroupedInventory class that has the item and quantity. Then, we update the individual Weapons and Potions properties whenever the base Inventory list changes (items are added or removed).

      The local map might be a little tricky – especially if you want to have a different image for each room, like we do for each location in the world map. The easiest way would just be to have a single image for each location’s local map. Create a new LocalMapFileName property in the Location class, and populate it in World.cs. Then, create a new LocalMap.cs form (in the UI project) that works like WorldMap.cs, but displays the image for the LocalMapFileName of the Player’s CurrentLocation.

      Do you want the player to be able to have a chest in each location, or only one chest in the whole world? If the player can have more than one chest, can they store different items in each chest? You might want to store the chests information similar to the Quests or InventoryItems, like this: https://gist.github.com/ScottLilly/88948812f277728fc5e6d5b2771e0b59. Then, Create a Chest class that has a LocationID property and a property that is a list of inventory items. In the Player class, create a new Chests property (datatype is a list of Chest objects). When the Player moves to a location, see if they have a chest with that LocationID.

      I’d probably have to see the code for the datagrid button problem. I’m not completely clear on it. When your computer is re-assembled, if you still have questions, can you upload your version of the program to GitHub or Dropbox, so I can look at it?

Leave a Reply

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