Mittens 2018 will be in Boston this September. There are three spaces left, so check out the thread for details!

Author Topic: Introducing AGS 3.4.0 (read-only explanation topic)  (Read 7841 times)

Crimson Wizard

  • Local Moderator
  • AGS Project Tracker Admins
    • Best Innovation Award Winner 2013, for spearheading the AGS 3.3.0 project
    •  
    • Lifetime Achievement Award Winner
    •  
    • Crimson Wizard worked on a game that was nominated for an AGS Award!
      Crimson Wizard worked on a game that won an AGS Award!
AGS 3.4.0
Introductory topic



AGS 3.4.0 is the next version of Adventure Game Studio Editor and Engine, being in development since mid-2014. Currently it is in relatively stable stage (you actually can make games with it), but its main planned features are not yet fully implemented, therefore we call it Alpha version.

As previously, we try to keep backwards compatibility with previous versions, meaning that:
1. You should be able to load your game project made with previous versions of AGS Editor, and continue to work on it, possibly with only minor changes to script (or enabling compatibility mode in General Settings).
2. You should be able to run your previously compiled game with new 3.4.0 engine without recompilation.

This thread is meant to serve for preliminary acquaintance with the new version.


The notable features of AGS 3.4.0 are:

* Custom resolutions. Since AGS 3.4.0 you will be able to create games with any size, and run it in any display resolution using existing scaling filters.
* Extended build system with support to build game for multiple platforms. AGS 3.4.0 supports extensions that let it compile games for additional platforms (e.g. Linux). They are optional, and require installing extra files to AGS folder, as well as enable them in project's General Settings.
* Custom properties are now mutable. Custom properties may now be changed at runtime.
* Extended AGS Script. A number of new commands and constructions were added to AGS script language, such as user-defined managed structs, "for" and "switch" blocks. Few existing features were improved, for example now it is possible to define static extender functions and have dynamic arrays in structs.
* Extended Dialog Options customization. You may now easily create custom rendered dialog options that animate and react to key presses.

TO BE UPDATED
« Last Edit: 22 Jan 2017, 13:39 by Crimson Wizard »

Crimson Wizard

  • Local Moderator
  • AGS Project Tracker Admins
    • Best Innovation Award Winner 2013, for spearheading the AGS 3.3.0 project
    •  
    • Lifetime Achievement Award Winner
    •  
    • Crimson Wizard worked on a game that was nominated for an AGS Award!
      Crimson Wizard worked on a game that won an AGS Award!
Re: AGS 3.4.0 (Alpha) - Work In Progress
« Reply #1 on: 31 Jul 2015, 11:51 »
Custom Resolutions

AGS 3.4.0 lets you choose literally any resolution (width and height) for your game. It does not mean, that every variant will work well, but reasonable ones should.
There is no restriction to width/height relation, you could even make "portrait orientation" games with height greater than width.

An example of 180x400 game made in AGS (mockup scenery used)


Inside your project, resolution is now set not with drop-down list, but with a slightly more advanced dialog:



Select "Resolution" option and click on "..." button



In the dialog either choose one of the presets, or type in your own width & height, then press OK.


Free display resolutions

To accompany custom game resolutions, AGS engine is now capable to run the game in any display mode supported by your monitor and graphics card.

The WinSetup utility was modified with different graphics options added. At the moment we are in the process of testing this design out. It may be simplified in future updates to make it easier for players to quickly set it up the way they want.



Currently you may choose the fullscreen display mode by setting following parameters:
* Mode: this determines the screen size your monitor will set to run the game;
* Filter: this determines which algorithm will be used to scale things up or down;
* Scaling (multiplier): this determines how much the game will be scaled up or down inside the screen;

For better results I recommend using following combinations:
1. If you feel selecting exact resolution more suitable for you, then choose wanted Mode and set scaling multiplier to "Max fit". This will scale the game by the maximal integer multiplier that fits in the Mode.
2. If it is rather convenient for you to select scaling (e.g. if you wish exactly x2 or x3 size of the game), then choose wanted scaling multiplier, and set "Bind to game scaling (force desktop ratio)" in the Modes list.
"Force desktop ratio" means that the game will refer to your desktop aspect ratio to choose gfx mode that looks better on your monitor.


With some of the display modes your game won't take all screen space if scaled up by an integer multiplier. In such case the black borders will appear around the game to fill the extra space. If you do not like to have these borders, you may enable couple of extra options on "Advanced" panel:



* "Stretch to fit screen" will make your game stretch more until it fills whole screen.
* "Keep aspect ratio" will ensure that the game's aspect ratio will be kept when stretching, so that image will still look proportional.
Latter option may cause some of the black borders to remain.

On thing to remember when using "stretch" options: the random stretching of the low-resolution game may cause image look slightly skewed (the higher resolution is, the less noticeable this effect will be).


On letterboxed games

AGS supports making your game letterboxed (I call it "letterbox-by-design"), implicitly increasing its height and automatically adding black borders to all rooms at runtime. This mode is enabled by setting "(Setup) -> Enable letterbox mode" property in General Settings.
This property had always had a limited functionality and worked only for 320x200 and 640x400 games. It was kind of a hack really, therefore we do not extend it on custom resolutions. Currently it remains only for backwards compatibility (and still works only with two mentioned resolutions).

In the future we may add better support for automatic letterboxing (if there would be demand for it); for now I recommend making explicit black borders in your game rooms and modify GUI appropriately instead, if you wish to achieve this effect for other resolutions.


On High and Low resolution type

In older versions of AGS there was a distinction between "High" and "Low" resolutions. 320x200 and 320x240 were considered "low", 640x400, 640x480, 800x600 and 1024x768 were considered "high" ones.
Such project items as Sprites, Fonts and Room backgrounds depended on this algorithm to know whether they should be automatically scaled.

For example, Sprites have "Resolution" property which let you set whether they were created for "High" or "Low" one. If the Sprite's resolution type does not match the game's, the sprite will be scaled up or down, respectively.
Similarly, there is a global "Text output -> Fonts designed for 640x480" setting in game project. Normally, for the High-res game the fonts will be scaled up. Enabling this option will stop them being stretched.

To support backwards-compatibility, following rule is applied for aforementioned properties:
* The resolution which has width equal or lower than 320 AND height equal or lower than 240 is considered to be "low-res".
* The resolution which has any of the sides higher; e.g width higher than 320 OR height higher than 240 - is considered to be "hi-res".

As you may realize, this distinction is pretty forced. I should underline, that it exists only to support old behavior for standard resolutions. It is possible that in future we will change how these properties work in the new game projects.




TO BE CONTINUED...
« Last Edit: 03 Aug 2015, 16:58 by Crimson Wizard »

Crimson Wizard

  • Local Moderator
  • AGS Project Tracker Admins
    • Best Innovation Award Winner 2013, for spearheading the AGS 3.3.0 project
    •  
    • Lifetime Achievement Award Winner
    •  
    • Crimson Wizard worked on a game that was nominated for an AGS Award!
      Crimson Wizard worked on a game that won an AGS Award!
Re: AGS 3.4.0 (Alpha) - Work In Progress
« Reply #2 on: 03 Aug 2015, 15:13 »
Building for multiple platforms

AGS 3.4.0 can build your game for more than one operating system. This is configured with this new option in General Settings (Compiler -> Build target platforms):



Select the systems you like to build the game for by pressing on "drop down" button to the right and checking or unchecking items in the list:



The contents of this list depends on what Editor components are installed. Editor has Windows build component by default, but you may also install Linux component to create Linux versions of your game.
"DataFile" option cannot be unchecked. It builds main game data file, which is used when making game for other systems.
Other available build targets may be selected in any combination.

This selection only affects doing full build (Build -> Build EXE, or Build -> Rebuild all files). When running in test mode only Windows version is built always.


The "Compiled" folder has changed. Now each build target has its own subdirectory: "Windows", "Linux", etc. To distribute your game - pack/copy contents of corresponding folder(s).

Example of "Compiled" folder contents:


Example of "Compiled/Windows" folder contents




Any extra files put into Compiled folder will be copied to every build subfolder, when the game is compiled. This way you may distribute any additional files your game needs.

You may also use the contents of "Compiled" folder itself (without subdirectories) to distribute game data files alone, without game executable. Then you may use stand-alone AGS engine on any system to run this game data.


This is important to mention, that when you run setup program from the Editor (Build -> Run game setup...), you will be modifying Windows config only. Same config will be used when you are test-running your game from the Editor (Build -> Run).
For other build configs (Linux, etc) you will have to modify acsetup.cfg file yourself right now (use plain text editor for that).


NOTE: for now you may still use old building system if you go to Editor's Preferences (File -> Preferences) and check "Use legacy compiler" option. As soon as we make sure that the new building system works well, this check will be removed forever though.



TO BE CONTINUED...
« Last Edit: 03 Aug 2015, 15:18 by Crimson Wizard »

Crimson Wizard

  • Local Moderator
  • AGS Project Tracker Admins
    • Best Innovation Award Winner 2013, for spearheading the AGS 3.3.0 project
    •  
    • Lifetime Achievement Award Winner
    •  
    • Crimson Wizard worked on a game that was nominated for an AGS Award!
      Crimson Wizard worked on a game that won an AGS Award!
Re: AGS 3.4.0 (Alpha) - Work In Progress
« Reply #3 on: 03 Aug 2015, 16:57 »
Dynamic Custom Properties

Custom properties were convenient way to bind additional descriptions to game objects, such as Characters, Room Objects and Inventory Items, which may then be used in script. Unfortunately, they had two limitations: there could only be 30 of them for the whole project, and they could not be changed at runtime.

AGS 3.4.0 removes both of these restrictions. This lets you to extend named object types with any number of extra attributes.

Changed system limits
ItemOld limitNew limit
Custom properties30unlimited
Custom property name lengths20unlimited
Custom property value lengths (for strings)500unlimited

You may now change property value at runtime from script. The related script functions are added to all classes that already supported GetProperty() and GetTextProperty(), that is - Room, InventoryItem, Hotspot, RoomObject and Character:
Code: Adventure Game Studio
  1.   /// Sets an integer (or boolean) custom property.
  2.   static bool SetProperty(const string property, int value);
  3.   /// Sets a text custom property.
  4.   static bool SetTextProperty(const string property, const string value);
  5.  
They return 'true' if the property value was changed, or 'false' if such property does not exist, or property type is incorrect (like setting text value for integer property).


The values set this way will be kept for the duration of the game, unless modified again.
Also, Room, Hotspot and RoomObject property values are reset to defaults when ResetRoom() is called.




TO BE CONTINUED...
« Last Edit: 03 Aug 2015, 16:59 by Crimson Wizard »

Crimson Wizard

  • Local Moderator
  • AGS Project Tracker Admins
    • Best Innovation Award Winner 2013, for spearheading the AGS 3.3.0 project
    •  
    • Lifetime Achievement Award Winner
    •  
    • Crimson Wizard worked on a game that was nominated for an AGS Award!
      Crimson Wizard worked on a game that won an AGS Award!
Re: AGS 3.4.0 (Alpha) - Work In Progress
« Reply #4 on: 23 Oct 2015, 15:18 »
Improved Custom Dialog Options rendering

Custom Dialog Options rendering system was extended to give game developers more freedom in setting it up. In the original variant the callback behavior was strictly tied to mouse movement and clicks. It now supports wider range of callbacks, covering all kinds of events that may occur when Dialog Option GUI is on screen.

To achieve this following changes were implemented.

1. Support for two more callbacks added:
Code: Adventure Game Studio
  1.   // runs each tick when dialog options are on screen
  2.   void dialog_options_repexec(DialogOptionsRenderingInfo *info);
  3.   // runs when user pressed a key while dialog options are on screen
  4.   void dialog_options_key_press(DialogOptionsRenderingInfo *info, eKeyCode key);
  5.  

The "dialog_options_get_active" callback is now NOT called, at all. It is supported only for backwards compatibility when running old games.
You will need to slightly change the logic of your script. In most cases it will be enough to simply rename "dialog_options_get_active" to "dialog_options_repexec".

2. Custom dialog options are no longer have automated reaction on mouse click and movement. Instead, following functions are added to the DialogOptionsRenderingInfo struct:
Code: Adventure Game Studio
  1.   bool RunActiveOption(); // runs the active dialog option (defined with ActiveOptionID property)
  2.   void Update(); // forces dialog options to redraw itself ("dialog_options_render" callback will be called)
  3.  

The "dialog_options_mouse_click" is now called always, even if user clicks on the option. In the most common case you should just call RunActiveOption() from there.



In short, scripting custom dialog options requires a bit more work on gamedev part, but is much more versatile now.

To prove this point, two basic examples follow.



Example A. Classic mouse controls
Add spoiler tag for Hidden:
Code: Adventure Game Studio
  1. int dlg_opt_color = 14;
  2. int dlg_opt_acolor = 13;
  3. int dlg_opt_ncolor = 4;
  4.  
  5. function dialog_options_get_dimensions(DialogOptionsRenderingInfo *info)
  6. {
  7.     // Create a 200x200 dialog options area at (50,100)
  8.     info.X = 50;
  9.     info.Y = 100;
  10.     info.Width = 200;
  11.     info.Height = 200;
  12. }
  13.  
  14. function dialog_options_render(DialogOptionsRenderingInfo *info)
  15. {
  16.     info.Surface.Clear(dlg_opt_color);
  17.     int i = 1,  ypos = 0;
  18.     // Render all the options that are enabled
  19.     while (i <= info.DialogToRender.OptionCount)
  20.     {
  21.         if (info.DialogToRender.GetOptionState(i) == eOptionOn)
  22.         {
  23.             if (info.ActiveOptionID == i)
  24.                 info.Surface.DrawingColor = dlg_opt_acolor;
  25.             else
  26.                 info.Surface.DrawingColor = dlg_opt_ncolor;
  27.             info.Surface.DrawStringWrapped(5, ypos, info.Width - 10,
  28.                     eFontFont0, eAlignLeft, info.DialogToRender.GetOptionText(i));
  29.             ypos += GetTextHeight(info.DialogToRender.GetOptionText(i), eFontFont0, info.Width - 10);
  30.         }
  31.         i++;
  32.     }
  33. }
  34.  
  35. function dialog_options_repexec(DialogOptionsRenderingInfo *info)
  36. {
  37.     info.ActiveOptionID = 0;
  38.     if (mouse.y < info.Y || mouse.y >= info.Y + info.Height ||
  39.         mouse.x < info.X || mouse.x >= info.X + info.Width)
  40.     {
  41.         return; // return if the mouse is outside UI bounds
  42.     }
  43.  
  44.     int i = 1, ypos = 0;
  45.     // Find the option that corresponds to where the player clicked
  46.     while (i <= info.DialogToRender.OptionCount)
  47.     {
  48.         if (info.DialogToRender.GetOptionState(i) == eOptionOn)
  49.         {
  50.             ypos += GetTextHeight(info.DialogToRender.GetOptionText(i), eFontFont0, info.Width - 10);
  51.             if ((mouse.y - info.Y) < ypos)
  52.             {
  53.                 info.ActiveOptionID = i;
  54.                 return;
  55.             }
  56.         }
  57.         i++;
  58.     }
  59. }
  60.  
  61. function dialog_options_mouse_click(DialogOptionsRenderingInfo *info, MouseButton button)
  62. {
  63.     if (info.ActiveOptionID > 0)
  64.         info.RunActiveOption();
  65. }
  66.  



Example B. Keyboard controls
Add spoiler tag for Hidden:
Code: Adventure Game Studio
  1. int dlg_opt_color = 14;
  2. int dlg_opt_acolor = 13;
  3. int dlg_opt_ncolor = 4;
  4.  
  5. function dialog_options_get_dimensions(DialogOptionsRenderingInfo *info)
  6. {
  7.     // Create a 200x200 dialog options area at (50,100)
  8.     info.X = 50;
  9.     info.Y = 100;
  10.     info.Width = 200;
  11.     info.Height = 200;
  12.     info.ActiveOptionID = 1; // set to first option
  13. }
  14.  
  15. function dialog_options_render(DialogOptionsRenderingInfo *info)
  16. {
  17.     info.Surface.Clear(dlg_opt_color);
  18.     int i = 1,  ypos = 0;
  19.     // Render all the options that are enabled
  20.     while (i <= info.DialogToRender.OptionCount)
  21.     {
  22.         if (info.DialogToRender.GetOptionState(i) == eOptionOn)
  23.         {
  24.             if (info.ActiveOptionID == i)
  25.                 info.Surface.DrawingColor = dlg_opt_acolor;
  26.             else
  27.                 info.Surface.DrawingColor = dlg_opt_ncolor;
  28.             info.Surface.DrawStringWrapped(5, ypos, info.Width - 10,
  29.                     eFontFont0, eAlignLeft, info.DialogToRender.GetOptionText(i));
  30.             ypos += GetTextHeight(info.DialogToRender.GetOptionText(i), eFontFont0, info.Width - 10);
  31.         }
  32.         i++;
  33.     }
  34. }
  35.  
  36. function dialog_options_key_press(DialogOptionsRenderingInfo *info, eKeyCode keycode)
  37. {
  38.     if (keycode == eKeyUpArrow && info.ActiveOptionID > 1)
  39.         info.ActiveOptionID = info.ActiveOptionID - 1;
  40.     if (keycode == eKeyDownArrow && info.ActiveOptionID < info.DialogToRender.OptionCount)
  41.         info.ActiveOptionID = info.ActiveOptionID + 1;
  42.     if (keycode == eKeyReturn || keycode == eKeySpace)
  43.         info.RunActiveOption();
  44. }
  45.  


TO BE CONTINUED...
« Last Edit: 24 Sep 2017, 15:01 by Crimson Wizard »

Crimson Wizard

  • Local Moderator
  • AGS Project Tracker Admins
    • Best Innovation Award Winner 2013, for spearheading the AGS 3.3.0 project
    •  
    • Lifetime Achievement Award Winner
    •  
    • Crimson Wizard worked on a game that was nominated for an AGS Award!
      Crimson Wizard worked on a game that won an AGS Award!
Re: AGS 3.4.0 (Alpha) - Work In Progress
« Reply #5 on: 23 Oct 2015, 20:15 »
Scripting improvements

In version 3.4.0 AGS scripting language is enriched with new commands and constructs.



Do...While loops

In addition to the previously existing "while" loop, the "do...while" loop construct is now supported. Unlike "while", "do...while" runs the first iteration *before* evaluating the condition.

Example:
Add spoiler tag for Hidden:
Code: Adventure Game Studio
  1. x = 1;
  2. do
  3. {
  4.     x++;
  5.     Display("%d", x);
  6. } while(x < 1);
  7.  

The loop above will run once.



For loop

In addition to a "while" loop, you can now use a "for" loop. The syntax is:
Code: Adventure Game Studio
  1. for([initialisation];[condition];[increment])
  2.         [single statement to repeat];
  3.        
  4. for([initialisation];[condition];[increment])
  5. {
  6.     [loop body]
  7. }
  8.  

Examples:
Add spoiler tag for Hidden:
Code: Adventure Game Studio
  1. for(player.x = 0; player.x < 100; player.x++)
  2.     Wait(1);
  3.  
  4.  
  5. for(int i = 10; i > 0; i--)
  6. {
  7.     Display("i = %d", i);
  8. }
  9.  



Break and continue statements

You can now break out of loops (any kind of loops) using the "break" statement.

Example:
Add spoiler tag for Hidden:
Code: Adventure Game Studio
  1. Code: Adventure Game Studio
  2. i = length - 1;
  3. while(i >= 0)
  4. {
  5.     if(page[i] == target)
  6.         break;
  7.     i--;
  8. }
  9.  

This will halt the loop when a match is found or leave i as -1 if there was no match.

You can also skip the rest of loop and go straight to the next iteration using the "continue" statement.

Example:
Add spoiler tag for Hidden:
Code: Adventure Game Studio
  1. for(x = 0; x < 100; x++)
  2. {
  3.     if(x % 2 == 0)
  4.         continue;
  5.     Display("%d", x);
  6. }
  7.  

This will display only odd numbers between 0 and 100.


Switch statement

Added support for "switch" statement, which may serve as a replacement for multiple "if/else if" coming in sequence.
Strings and other variable types are allowed to be checked in switch condition. Standard case statement features like fallthrough and break are also supported.

Example:
Add spoiler tag for Hidden:
Code: Adventure Game Studio
  1. String x = "a";
  2.  
  3. switch(x)
  4. {
  5. case "a":
  6.     Display("X is A");
  7. case "b": // fall-through
  8.     Display("X is A or B");
  9.     break; // break here
  10. case "c":
  11.     Display("X is C");
  12. case "d": // fall-through
  13. default: // fall-through
  14.     Display("X is C, D, or something except A and B");
  15. }
  16.  


Dynamic Arrays in Structs

Dynamic arrays are now permitted inside structs.

Example:
Add spoiler tag for Hidden:
Code: Adventure Game Studio
  1. struct DieRoll
  2. {
  3.     int BaseModifier;
  4.     int DieCount;
  5.     int Dice[ ];
  6.     import function GetTotalValueOfRoll();
  7. };
  8.  
  9. function PrepareDice()
  10. {
  11.     DieRoll a;
  12.  
  13.     a.DieCount = 3;
  14.     a.Dice = new int[a.DieCount];
  15.     a.Dice[0] = 6; // d6
  16.     a.Dice[1] = 6; // d6
  17.     a.Dice[2] = 8; // d8
  18.     ...
  19. }
  20.  

And the dynamic array "Dice" can be initialised and used like any other dynamic array.


Dynamic arrays returned by the Struct member function.

It is now possible to define a member function of a struct, that returns dynamic array.

Example:
Add spoiler tag for Hidden:
Code: Adventure Game Studio
  1. struct MyClass
  2. {
  3.         int Max;
  4.         int Arr[];
  5.        
  6.         import void  InitArray(int max);
  7.         import int[] GetArray();
  8.         import int   GetArrayLength();
  9. };
  10.  
  11. void MyClass::InitArray(int max)
  12. {
  13.         this.Max = max;
  14.         this.Arr = new int[this.Max];
  15.         int i;
  16.         for (i = 0; i < this.Max; i++)
  17.                 this.Arr[i] = i;
  18. }
  19.  
  20. int[] MyClass::GetArray()
  21. {
  22.         return this.Arr;
  23. }
  24.  
  25. int MyClass::GetArrayLength()
  26. {
  27.         return this.Max;
  28. }
  29.  
  30. function game_start()
  31. {
  32.         MyClass my_obj;
  33.         my_obj.InitArray(5);
  34.         int get_arr[] = my_obj.GetArray();
  35.         int i;
  36.         for (i = 0; i < my_obj.GetArrayLength(); i++)
  37.                 Display("#%i = %i", i, get_arr[i]);
  38. }
  39.  


Static extender functions

It is now possible to create static extender functions, that is extender function that do not require an actual object.

The syntax is:
Code: Adventure Game Studio
  1. [return_type] function_name(static [Struct_Name], [parameters]);
  2.  

Example:
Add spoiler tag for Hidden:
Code: Adventure Game Studio
  1. int AbsInt(static Maths, int value)
  2. {
  3.     if (value < 0)
  4.         value = 0 - value;
  5.      
  6.     return value;
  7. }
  8.  

This works in the same way as the normal extender method syntax (e.g. this Character *) but for static methods. The above code will define a new method in the static Maths called AbsInt. You can then import it in a header:
Code: Adventure Game Studio
  1. import int AbsInt(static Maths, int value);
  2.  

And then use it elsewhere in your code just like it were a built-in Maths function:
Code: Adventure Game Studio
  1. int x = Maths.AbsInt(-3);
  2.  


More assignment operators

The following C-style assignment operators are now supported in addition to += and -=:

Code: Adventure Game Studio
  1. *=   // Multiply by and assign
  2. /=   // Divide by and assign
  3. &=   // Bitwise AND and assign
  4. |=   // Bitwise OR and assign
  5. ^=   // Bitwise XOR and assign
  6. <<=  // Bitshift left and assign
  7. >>=  // Bitshift right and assign
  8.  


Improvements to macros

#define command can now refer to other #define'd constants. Like in VC++, #define symbol expansion only needs to make sense at the time of reference. Also like VC++, the order of previously defined constants isn't important.

Example:
Add spoiler tag for Hidden:
Code: Adventure Game Studio
  1. #define RED    GREEN
  2. #define BLUE   456
  3. #define GREEN  BLUE
  4. Display("%d", RED); // Prints 456
  5. #undef BLUE
  6. #define BLUE  123
  7. Display("%d", RED); // Prints 123
  8.  

Important: To prevent circular references, a #define cannot refer to itself or anything previously used to expand the #define symbol.


Code Regions

You can now define an arbitrary region for code folding in your script. Then the AGS editor will allow you to fold and unfold this region as though it were a code block. Regions are purely cosmetic feature and have no effect on compiled code.

Example:
Add spoiler tag for Hidden:
Code: Adventure Game Studio
  1. #region MyRegion
  2. do stuff;
  3. do stuff;
  4. do stuff;
  5. #endregion MyRegion
  6.  


TO BE CONTINUED...