1. Hey! Guest! The 35th GMC Jam will take place between November 28th, 12:00 UTC - December 2nd, 12:00 UTC. Why not join in! Click here to find out more!
    Dismiss Notice

Simple Metroidvania Map Implementation

Discussion in 'Tutorials' started by Roman P., Aug 30, 2017.

  1. Roman P.

    Roman P. Member

    Joined:
    Mar 1, 2017
    Posts:
    225
    GM Version: Studio 2
    Target Platform: Windows
    Download: N/A (see code below)
    Links: N/A

    Heya! I had just posted recently an update on my ‘metroidvania’-like games progress and I had some people asking me how I had made the map for it so I decided to write this down.
    This should be easy to follow for someone beginner/intermediate (since I’m one myself).
    My way is probably not the most efficient, nonetheless its quite simple and works so let’s get to it.

    Here it is in action:


    1.png

    Tutorial on the dropbox
    https://www.dropbox.com/s/jgdko0i6sbbkl4n/GM_Metroidvania.docx?dl=0
     

    Attached Files:

    • 2.png
      2.png
      File size:
      132 bytes
      Views:
      468
    • 3.png
      3.png
      File size:
      164 bytes
      Views:
      466
    • 4.png
      4.png
      File size:
      102.7 KB
      Views:
      476
    • 5.png
      5.png
      File size:
      20.7 KB
      Views:
      477
    Last edited: Jun 19, 2018
  2. Snayff

    Snayff Guest

    Thank you for sharing this.
     
  3. computercoder

    computercoder Member

    Joined:
    Aug 13, 2017
    Posts:
    10
    This is pretty good stuff. Good job! :)
     
  4. nlolotte

    nlolotte Member

    Joined:
    Aug 3, 2016
    Posts:
    465
    Thanks man, this is such a simple but effective method. How would you go about being able to reveal portions of the map if you have the right map - like in castlevania where you find castlemap 1 and it shows you the first few rooms
     
  5. Roman P.

    Roman P. Member

    Joined:
    Mar 1, 2017
    Posts:
    225
    i think youll need an extra value in the array like 2 instead of just using 0,1

    once you aquire the 'map' set the values of those cells to 2 if undiscovered(0). if area was already discovered keep it at 1.

    use different drawing code for 2.

    this would get a bit more complicated as youll need to split up the image and draw different sprites for different cells.

    this is just a quick thought though.
     
    Last edited: Oct 20, 2017
  6. nlolotte

    nlolotte Member

    Joined:
    Aug 3, 2016
    Posts:
    465
    I’ll have a play after work and see what I can come up with. Thanks again dude.
     
  7. Galax

    Galax Guest

    Very useful! Thanks :D
     
  8. Shaybe

    Shaybe Guest

    I am trying to add this to my game. Does anyone have the images that were linked to mouthfulgames? They don’t appear to be working anymore so I’m missing a few chunks of what to code!
     
  9. nlolotte

    nlolotte Member

    Joined:
    Aug 3, 2016
    Posts:
    465
    I don't have the images but I have the code?
     
  10. Do you mind sharing the missing code from this thread with me?
     
  11. Do you have the missing images for this thread? Just wondering.
     
  12. Roman P.

    Roman P. Member

    Joined:
    Mar 1, 2017
    Posts:
    225
    image links updated, sorry for that guys
     
  13. yarf1985

    yarf1985 Member

    Joined:
    May 5, 2018
    Posts:
    1
    Roman P.
    Very useful Code but i still figuring out how update this variables : global.gridX and global.gridY.

    These variables along with ‘local_x’ and ‘local_y’ are going to be used in unlocking cells/displaying current location calculations.

    What we’re going to do with these variables is update them every time a new room is entered.

    This is the only value that is going to be hard-coded and you want to update it in each rooms creation code to represent that rooms top-left cells location on the grid.
     
  14. Roman P.

    Roman P. Member

    Joined:
    Mar 1, 2017
    Posts:
    225
    you declare/reassign them in each rooms creation code. they are hardcoded.
    they values are that of an 'X' and 'Y' of your map (not room coordinates). specifically they are an 'X' and 'Y' current rooms top left corner.

    im writting a tool atm that will let you create your own map from pieces and then will automatically populate the gamemaker project with the rooms, transitions, global.XY variables to save a lot of time if interested. (got sick after manually doing 3rd of my map lol :D)
     
    yarf1985 likes this.
  15. 3dgeminis

    3dgeminis Member

    Joined:
    Jun 21, 2016
    Posts:
    133
    Great tutorial. Some images are missing.
     
    jonjons likes this.
  16. jonjons

    jonjons Member

    Joined:
    May 10, 2017
    Posts:
    294
    yep images and code missing waste of time
     
  17. Roman P.

    Roman P. Member

    Joined:
    Mar 1, 2017
    Posts:
    225
  18. jonjons

    jonjons Member

    Joined:
    May 10, 2017
    Posts:
    294
    great thanks for sharing
    you can try using postimage.org this way the images will stay forever.
    https://postimg.cc/gallery/3ht04f1ki/bc8d5395/

    https://postimg.cc/gallery/3i6bq9os2/a7d72035/
    //---------------------------------------------------------

    Heya! I had just posted recently an update on my ‘metroidvania’-like games progress and I had some people asking me private how I had made the map for it so I decided to write this down.
    This should be easy to follow for someone beginner/intermediate (since I’m one myself).
    My way is probably not the most efficient, nonetheless its quite simple and works so let’s get to it.
    ⦁ Creating Needed Images.
    The kind of the map I’m going to talk about is not randomly generated but plain user made.
    First image we’ll need is the actual map. I have drawn this map myself in Aseprite. Each cell you see is 8x8 pixels. Every room on the map is an individual room in GameMaker and every cell is equivalent of 400x240 pixels in the actual game. You’ll be able to adjust/change this to fit your own game.
    [​IMG]
    Second image we’ll need is that of an empty cell. So in this case a plain grey rectangle size of 8x8.
    [​IMG]
    And the last image that we’ll need is that to represent a player’s current position on the map.
    [​IMG]
    With this one you need to make sure its same size as a cell as well. In my case 8x8. The actual icon in mines is smaller than 8x8 though so what I did is centred it in the 8x8 frame leaving transparency around it.
    Also with all the images make sure that their origin point is set to top left.
    ⦁ Understanding How the Map Is Going to Be Drawn.
    To draw the map on the screen we’re going to use a Draw GUI event.
    Things are going to get drawn in the certain order.
    ⦁ First, the original map is drawn.
    ⦁ Second, the grey squares are going to be drawn on top of every cell that hasn’t been explored yet thus covering those parts of map completely.
    ⦁ Third, the player position icon is drawn on top of everything else.
    ⦁ Lastly, we’re going to draw the ‘Total %’ unlocked in the bottom left of the screen.
    ⦁ Understanding How Exploration/Map Data is going to be stored.

    [​IMG]

    You can see in this image that my map fits into a grid with a height of 26 and width of 46.
    So the logical thing to do is to store the exploration data in a 2D array.
    So we’re going to create a 2D array mapGrid with first entry [0, 0] and last being [25, 45].
    The actual values that array entries are going to hold are either 0 or 1.
    0 – cell has not been explored(we’re going to cover it with grey rectangle)
    1 – cell has been explored (no rectangle is drawn on top of it)
    Remember that arrays start at zero and not one. So for convenience purposes we’re going to keep track of our cells in same way. Like so…
    [​IMG]

    ⦁ Implementation.
    To implement a map into the game we’ll need to create a persistent controller object that will
    ⦁ hold and carry our 2d array
    ⦁ update the array as we explore our game world
    ⦁ draw the unlocked map with player location and percentage unlocked on demand
    I create/destroy this object whenever the player object is created/destroyed (starting/loading a game or dying in the game). If you’ve ever made a ‘Pause’ button or something similar you’ll be familiar with some concepts here.
    If you’re making a ‘metroidvania’ type of game I’ll also assume that it’s reasonable size and you’d want to save the map outside the object as well so you can resume your game later. I personally use plain .ini files and use an .ini file to initialize the array inside my map controller objects create event. I also dump the contents of the array back into an .ini file on save points.
    //CREATE EVENT
    [​IMG]

    Let’s look at the create event we have here.
    ⦁ display_set_gui_size(400, 240);
    ⦁ display_set_gui_maximise(2, 2, 0, 0);
    This sets GUI layer to be the same size as my in-game window size and then upscales it x2 to account for that my viewport is double the size of that. This is kind off specific to my game. Just make sure your GUI layer is same size as your game window.
    ⦁ map = false;
    ‘map’ is a variable that we’re going to trigger when we want to bring our map up.
    When map will be set to true all the other instances in the room apart from our controller object are going to be deactivated and the map is going to be drawn on the screen.
    When ‘map’ is false the object will be running in the background just doing checks for whether we’ve unlocked new segments.
    ⦁ percentage = 0;
    Well need this variable later in the Draw GUI event to calculate the percentage of map unlocked.
    ⦁ local_x = obj_player.x;
    ⦁ local_y = obj_player.y;
    ‘local_x’ and ‘local_y’ variables are going to be used further in unlocking cells/displaying current location calculations.
    The reason to create these variables instead of just referring to obj_player.x or y later on, is the reason to have access to these values once all other instances including obj_player are deactivated.
    ini_open(global.save_selected_temp)
    //MAP DATA TAKEN FROM SAVE FILE
    for (var i = 0; i <= 25; i++)
    {
    for (var j = 0; j <= 45; j++)
    {
    mapGrid[i, j] = ini_read_real("row" + string(i), string(j), 0);
    }
    }
    ini_close();
    And in this final part we initialise our whole array from a text file with a nested ‘for’ loop. If you don’t have/don’t want to load any map data from a file just run it like this
    for (var i = 0; i <= 25; i++)
    {
    for (var j = 0; j <= 45; j++)
    {
    mapGrid[i, j] = 0;
    }
    }
    Here’s how my .ini file looks like so you get an idea or what I’m doing in the mapGrid[i, j] = ini_read_real("row" + string(i), string(j), 0); line. And global.save_selected_temp is just a ‘’string’’ variable where I store my actual file name in this case “save_file_1.ini”.
    [​IMG]
    Another thing you need to be aware of with the array is how data is stored in relation to Grid.
    mapGrid[Y, X] – The Y coordinates are stored in the rows and X in columns, don’t mix this up.
    //GLOBAL VARIABLES
    To make the system work some hard-coding is necessary as the map is not generated, consists of different, unrelated to each other GameMaker rooms and doesn’t follow any repetitive patterns.
    I’ve managed to find a way to keep the hard-coding to the minimum.
    Ok, so somewhere in your game you’ll have to declare 2 global variables. I do it in the first rooms creation code same as other global variables and enumerators that I have.
    global.gridX and global.gridY.
    These variables along with ‘local_x’ and ‘local_y’ are going to be used in unlocking cells/displaying current location calculations.
    What we’re going to do with these variables is update them every time a new room is entered.
    This is the only value that is going to be hard-coded and you want to update it in each rooms creation code to represent that rooms top-left cells location on the grid.
    [​IMG]

    //STEP EVENT
    [​IMG]
    I’ll start with explaining the second if statement first.
    Basically, all it does is it brings up the map when select key is pressed on the gamepad by switching ‘map’ variable to true and vice versa. It also dims the volume and deactivates all instances in the room.
    Now the first if statement is where the magic happens. It runs every step updating the local_x and local_y variables (which we’ll need in the draw event because we won’t be able to access obj_player once objects are deactivated).

    mapGrid[(global.gridY+(local_y div 240)), (global.gridX+(local_x div 400))] = 1;

    And this piece of code a single entry of 1 is written into a specific location in the array that refers to our current location, thus marking the area ‘visited’.

    global.gridY – is the variable that we declared in the rooms creation code referring to rooms top left Y coordinate on the grid.
    local_y div 240 – calculates whether we have moved any cells down from the top left corner. local_y is our players y coordinate. div is the operator that checks how many times the first number fits into the second number. the number 240 is the height of the cell in the actual game. So, if players y coordinate is anywhere from 0 to 240, local_y div 240 will return 0. If it’s in the range from 240 to 480 it will return 1 etc.

    global.gridX and local_x div 400 – work on the same principle just reffering to X coordinates.
    Again, be aware how data is stored within the array in relation to Grid.
    mapGrid[Y, X] – The Y coordinates are stored in the rows and X in columns, don’t mix this up.


    //DRAW GUI EVENT
    [​IMG]

    And here’s where the drawing happens. This part is quite straight forward now and it only happens when ‘map’ is set to true.

    ⦁ First you see I set my font and alignment with draw_set_ functions that I will use to draw the percentage %, little details…
    ⦁ In the //DRAW BACKGROUND part I draw the whole map image starting at x 0, y 0 coordinates of GUI layer.
    ⦁ In the //DRAW OVERLAY part I iterate through the array with a nested for loop and check whether the entries are either 0 or 1.
    ⦁ If entry is a 0, I draw a rectangle over that cell. Look at 16 + 8*(j) bit. 16 is the offset from the top of my image to where the actual grid starts on the image. 8 is cell size, and by multiplying it against iteration and adding the offset we get the correct coordinates of where to draw. Same in the 16 + 8*(i) bit. Again, be aware on which side the x and y coordinates are represented.

    ⦁ If entry is 0, no rectangle is drawn so the cell stays visible. At the same time 1 is added to the percentage variable which we use in the next step.
    ⦁ //DRAW PERCENTAGE, this part just draws the percentage of uncovered map as the text in the bottom left of the screen. string(floor((percentage/635)*102)) + “%”.
    ⦁ So far the percentage variable has counted up all the cells in the array that have returned the value of 1. So to get actual percentage we divide that number by the total number of cells that can be discovered, which in my case is 635. Then we multiply it by a 100. In my case I chose to multiply by 102, so the whole map when uncovered would show 102%. Mind that the percentage variable needs to be reset back to 0 at the end of the step, cause otherwise it will keep increasing.
    ⦁ And the last bit draws the players current location on the map. This code is pretty much the same as the one in the step event that adds entries into the array by using global.gridX/Y and local_x/y variables.
     
    Last edited: Jul 15, 2018
    Niels likes this.
  19. jonjons

    jonjons Member

    Joined:
    May 10, 2017
    Posts:
    294
    Hi
    did you manage to get the global.gridY working ?
    mine is offset by 2 values global.gridY -2 goes to position 0. Everything else is working great
     
  20. How would I dump the contents of the array back into an .ini file on save points?
     
  21. Thanks for this amazing post Roman, but I'm stuck trying to dump the contents of the array back into an .ini file on save points. I didn't want to load the map data from a file so I used the mapGrid[i, j] = 0; approach.

    I'm trying to dump the contents of the array into an .ini file like this:

    ini_open("Map.ini");
    ini_write_real("Map Coordinates", "0", ds_grid_write(obj_map.mapGrid));
    ini_close();

    But I'm getting an error message: ds_grid_write argument 1 incorrect type (array) expecting a Number

    Any suggestions on how I can get this done?
     
  22. MrPr1993

    MrPr1993 Member

    Joined:
    Nov 22, 2016
    Posts:
    94
    Eyyy what a find. Thanks for sharing~
     
  23. Arara

    Arara Member

    Joined:
    Dec 2, 2018
    Posts:
    1
    Hey, can someone help me? I followed the instruction in dropbox but I only get empty screen when I press the map button.
    This would be really helpful for my project if I got it working.
    Edit: Never mind, stupid mistake got it fixed.
    Thank you for this awesome map!
     
    Last edited: Dec 7, 2018
  24. ShenTzu

    ShenTzu Member

    Joined:
    Oct 16, 2019
    Posts:
    1
    Sorry about necroing this thread, but the map system looks great and I'd really like to use it. Unfortunately the dropbox link is dead now, does anyone happen to have the tutorial? Thanks in advance!
     
  25. Roman P.

    Roman P. Member

    Joined:
    Mar 1, 2017
    Posts:
    225
    //---------------------------------------------------------

    Heya! I had just posted recently an update on my ‘metroidvania’-like games progress and I had some people asking me private how I had made the map for it so I decided to write this down.
    This should be easy to follow for someone beginner/intermediate (since I’m one myself).
    My way is probably not the most efficient, nonetheless its quite simple and works so let’s get to it.
    ⦁ Creating Needed Images.
    The kind of the map I’m going to talk about is not randomly generated but plain user made.
    First image we’ll need is the actual map. I have drawn this map myself in Aseprite. Each cell you see is 8x8 pixels. Every room on the map is an individual room in GameMaker and every cell is equivalent of 400x240 pixels in the actual game. You’ll be able to adjust/change this to fit your own game.
    [​IMG]
    Second image we’ll need is that of an empty cell. So in this case a plain grey rectangle size of 8x8.
    [​IMG]
    And the last image that we’ll need is that to represent a player’s current position on the map.
    [​IMG]
    With this one you need to make sure its same size as a cell as well. In my case 8x8. The actual icon in mines is smaller than 8x8 though so what I did is centred it in the 8x8 frame leaving transparency around it.
    Also with all the images make sure that their origin point is set to top left.
    ⦁ Understanding How the Map Is Going to Be Drawn.
    To draw the map on the screen we’re going to use a Draw GUI event.
    Things are going to get drawn in the certain order.
    ⦁ First, the original map is drawn.
    ⦁ Second, the grey squares are going to be drawn on top of every cell that hasn’t been explored yet thus covering those parts of map completely.
    ⦁ Third, the player position icon is drawn on top of everything else.
    ⦁ Lastly, we’re going to draw the ‘Total %’ unlocked in the bottom left of the screen.
    ⦁ Understanding How Exploration/Map Data is going to be stored.

    [​IMG]

    You can see in this image that my map fits into a grid with a height of 26 and width of 46.
    So the logical thing to do is to store the exploration data in a 2D array.
    So we’re going to create a 2D array mapGrid with first entry [0, 0] and last being [25, 45].
    The actual values that array entries are going to hold are either 0 or 1.
    0 – cell has not been explored(we’re going to cover it with grey rectangle)
    1 – cell has been explored (no rectangle is drawn on top of it)
    Remember that arrays start at zero and not one. So for convenience purposes we’re going to keep track of our cells in same way. Like so…
    [​IMG]

    ⦁ Implementation.
    To implement a map into the game we’ll need to create a persistent controller object that will
    ⦁ hold and carry our 2d array
    ⦁ update the array as we explore our game world
    ⦁ draw the unlocked map with player location and percentage unlocked on demand
    I create/destroy this object whenever the player object is created/destroyed (starting/loading a game or dying in the game). If you’ve ever made a ‘Pause’ button or something similar you’ll be familiar with some concepts here.
    If you’re making a ‘metroidvania’ type of game I’ll also assume that it’s reasonable size and you’d want to save the map outside the object as well so you can resume your game later. I personally use plain .ini files and use an .ini file to initialize the array inside my map controller objects create event. I also dump the contents of the array back into an .ini file on save points.
    //CREATE EVENT
    [​IMG]

    Let’s look at the create event we have here.
    ⦁ display_set_gui_size(400, 240);
    ⦁ display_set_gui_maximise(2, 2, 0, 0);
    This sets GUI layer to be the same size as my in-game window size and then upscales it x2 to account for that my viewport is double the size of that. This is kind off specific to my game. Just make sure your GUI layer is same size as your game window.
    ⦁ map = false;
    ‘map’ is a variable that we’re going to trigger when we want to bring our map up.
    When map will be set to true all the other instances in the room apart from our controller object are going to be deactivated and the map is going to be drawn on the screen.
    When ‘map’ is false the object will be running in the background just doing checks for whether we’ve unlocked new segments.
    ⦁ percentage = 0;
    Well need this variable later in the Draw GUI event to calculate the percentage of map unlocked.
    ⦁ local_x = obj_player.x;
    ⦁ local_y = obj_player.y;
    ‘local_x’ and ‘local_y’ variables are going to be used further in unlocking cells/displaying current location calculations.
    The reason to create these variables instead of just referring to obj_player.x or y later on, is the reason to have access to these values once all other instances including obj_player are deactivated.
    ini_open(global.save_selected_temp)
    //MAP DATA TAKEN FROM SAVE FILE
    for (var i = 0; i <= 25; i++)
    {
    for (var j = 0; j <= 45; j++)
    {
    mapGrid[i, j] = ini_read_real("row" + string(i), string(j), 0);
    }
    }
    ini_close();
    And in this final part we initialise our whole array from a text file with a nested ‘for’ loop. If you don’t have/don’t want to load any map data from a file just run it like this
    for (var i = 0; i <= 25; i++)
    {
    for (var j = 0; j <= 45; j++)
    {
    mapGrid[i, j] = 0;
    }
    }
    Here’s how my .ini file looks like so you get an idea or what I’m doing in the mapGrid[i, j] = ini_read_real("row" + string(i), string(j), 0); line. And global.save_selected_temp is just a ‘’string’’ variable where I store my actual file name in this case “save_file_1.ini”.
    [​IMG]
    Another thing you need to be aware of with the array is how data is stored in relation to Grid.
    mapGrid[Y, X] – The Y coordinates are stored in the rows and X in columns, don’t mix this up.
    //GLOBAL VARIABLES
    To make the system work some hard-coding is necessary as the map is not generated, consists of different, unrelated to each other GameMaker rooms and doesn’t follow any repetitive patterns.
    I’ve managed to find a way to keep the hard-coding to the minimum.
    Ok, so somewhere in your game you’ll have to declare 2 global variables. I do it in the first rooms creation code same as other global variables and enumerators that I have.
    global.gridX and global.gridY.
    These variables along with ‘local_x’ and ‘local_y’ are going to be used in unlocking cells/displaying current location calculations.
    What we’re going to do with these variables is update them every time a new room is entered.
    This is the only value that is going to be hard-coded and you want to update it in each rooms creation code to represent that rooms top-left cells location on the grid.
    [​IMG]

    //STEP EVENT
    [​IMG]
    I’ll start with explaining the second if statement first.
    Basically, all it does is it brings up the map when select key is pressed on the gamepad by switching ‘map’ variable to true and vice versa. It also dims the volume and deactivates all instances in the room.
    Now the first if statement is where the magic happens. It runs every step updating the local_x and local_y variables (which we’ll need in the draw event because we won’t be able to access obj_player once objects are deactivated).

    mapGrid[(global.gridY+(local_y div 240)), (global.gridX+(local_x div 400))] = 1;

    And this piece of code a single entry of 1 is written into a specific location in the array that refers to our current location, thus marking the area ‘visited’.

    global.gridY – is the variable that we declared in the rooms creation code referring to rooms top left Y coordinate on the grid.
    local_y div 240 – calculates whether we have moved any cells down from the top left corner. local_y is our players y coordinate. div is the operator that checks how many times the first number fits into the second number. the number 240 is the height of the cell in the actual game. So, if players y coordinate is anywhere from 0 to 240, local_y div 240 will return 0. If it’s in the range from 240 to 480 it will return 1 etc.

    global.gridX and local_x div 400 – work on the same principle just reffering to X coordinates.
    Again, be aware how data is stored within the array in relation to Grid.
    mapGrid[Y, X] – The Y coordinates are stored in the rows and X in columns, don’t mix this up.


    //DRAW GUI EVENT
    [​IMG]

    And here’s where the drawing happens. This part is quite straight forward now and it only happens when ‘map’ is set to true.

    ⦁ First you see I set my font and alignment with draw_set_ functions that I will use to draw the percentage %, little details…
    ⦁ In the //DRAW BACKGROUND part I draw the whole map image starting at x 0, y 0 coordinates of GUI layer.
    ⦁ In the //DRAW OVERLAY part I iterate through the array with a nested for loop and check whether the entries are either 0 or 1.
    ⦁ If entry is a 0, I draw a rectangle over that cell. Look at 16 + 8*(j) bit. 16 is the offset from the top of my image to where the actual grid starts on the image. 8 is cell size, and by multiplying it against iteration and adding the offset we get the correct coordinates of where to draw. Same in the 16 + 8*(i) bit. Again, be aware on which side the x and y coordinates are represented.

    ⦁ If entry is 0, no rectangle is drawn so the cell stays visible. At the same time 1 is added to the percentage variable which we use in the next step.
    ⦁ //DRAW PERCENTAGE, this part just draws the percentage of uncovered map as the text in the bottom left of the screen. string(floor((percentage/635)*102)) + “%”.
    ⦁ So far the percentage variable has counted up all the cells in the array that have returned the value of 1. So to get actual percentage we divide that number by the total number of cells that can be discovered, which in my case is 635. Then we multiply it by a 100. In my case I chose to multiply by 102, so the whole map when uncovered would show 102%. Mind that the percentage variable needs to be reset back to 0 at the end of the step, cause otherwise it will keep increasing.
    ⦁ And the last bit draws the players current location on the map. This code is pretty much the same as the one in the step event that adds entries into the array by using global.gridX/Y and local_x/y variables.
     

Share This Page

  1. This site uses cookies to help personalise content, tailor your experience and to keep you logged in if you register.
    By continuing to use this site, you are consenting to our use of cookies.
    Dismiss Notice