• 🏆 Texturing Contest #33 is OPEN! Contestants must re-texture a SD unit model found in-game (Warcraft 3 Classic), recreating the unit into a peaceful NPC version. 🔗Click here to enter!
  • It's time for the first HD Modeling Contest of 2024. Join the theme discussion for Hive's HD Modeling Contest #6! Click here to post your idea!

Antares' Hero Selection v1.3.2 (now with GUI and Lua)

This bundle is marked as pending. It has not been reviewed by a staff member yet.
Create stunning, cinematic hero selection screens and easily integrate them into your map!

Features:
  • Highly customizable.
  • Easy to interface.
  • Supports any number of heroes with multiple pages.
  • Repick, Restart, Ban phase, and All Random.
  • Shared and separate hero selection.
  • Open and concealed hero selection.
  • Different selection screens for each team.
  • Different hero rosters for each team.



Keeping three different versions of a giant system like this completely bug-free is almost impossible. Please help me by reporting any bugs you find!

This hero selection system allows players to get a full preview of the hero they're about to pick, with a mouse-over tooltip and a preview of their abilities. Heroes are represented as special effects, not as units, allowing for the hero to be shown locally, only to the player selecting it. Heroes can play animations and emotes when they are preselected and/or picked. The system has many additional features, such as All Random, Ban Phase, and Repick.

While the config is quite long, it is only because every part of the presentation can be customized. If you don't like any of the features shown in the showcase map, you can simply disable it. Getting a working system should be relatively easy, although setting up all your heroes may be tedious if you have a lot of them.

I recommend using this system in conjunction with Neat Text Messages.

Based on the Particle Party hero selection system.



JASS:
/*
    ==========================================================================================================================================================
                                                                Antares' Hero Selection

    ==========================================================================================================================================================
                                                                        A P I
    ==========================================================================================================================================================

    function InitHeroSelection takes nothing returns nothing                                                //Creates hero list, player list, and frames.
    function BeginHeroSelection takes nothing returns nothing                                               //Enforces camera and/or shows menu, based on config.
    function EnableHeroSelection takes nothing returns nothing                                              //Allows heroes to be picked.
    function EnablePlayerHeroSelection takes player whichPlayer, boolean enable returns nothing
    function EnablePlayerHeroPreselection takes player whichPlayer, boolean enable returns nothing
    function PlayerEscapeHeroSelection takes player whichPlayer returns nothing                             //Hides frames and unlocks camera for player.
    function PlayerReturnToHeroSelection takes player whichPlayer returns nothing                           //Allows player to repick his or her hero.
    function HeroSelectionAllRandom takes nothing returns nothing
    function HeroSelectionPlayerForceSelect takes player whichPlayer, Hero whichHero returns nothing
    function HeroSelectionPlayerForceRandom takes player whichPlayer returns nothing
    function HeroSelectionForceEnd takes nothing returns nothing                                            //Will give each player who hasn't picked a random hero.
    function BanHeroFromSelection takes Hero whichHero, boolean disable returns nothing                     //Hero will remain in the menu.
    function HeroSelectionSetTimeRemaining takes real time returns nothing
    function HeroSelectionAddTimeRemaining takes real time returns nothing
    function RestartHeroSelection takes nothing returns nothing                                             //For game restarts. Resets all variables and calls BeginHeroSelection. No one can be in hero selection at the time.
    function StartHeroBanPhase takes real timeLimit returns nothing                                         //Starts a ban phase with specified time limit, after which EnableHeroSelection is called. Requires MENU_INCLUDE_BAN_BUTTON.
    function StartHeroSelectionTimer takes real timeLimit, code callback returns nothing                    //Starts the timer that is displayed during hero selection with a custom callback function.
    function NoOneInHeroSelection takes nothing returns boolean
    function EveryoneHasHero takes nothing returns boolean

    ==========================================================================================================================================================
     
    How to import:

    Copy into empty triggers in your map:
    HeroSelectionDocumentation
    HeroSelectionConfig
    HeroSelectionCallbacks
    HeroDeclaration
    HeroSelection
   
    Import:
    HeroSelectionMenu.fdf
    CustomTooltip.fdf
    HeroSelectionTemplates.toc
    GeneralHeroGlow.mdx (optional)

    Copy Destructables:
    Hero No Shadow (optional)
    Hero Shadow (optional)
   
    ===========================================================================================

    Getting started:

    HeroSelection has a long config that you should go through from top to bottom, with the
    customization options becoming more and more specific as you progress. The fine-tuning
    section has a vast number of options, which may look overwhelming, but you do not need
    to edit any of them to get a working system.

    To get a working system, here is the To-Do List:

    1. Set up the terrain and the hero selection camera.
    2. Import your heroes into HeroDeclaration and set the fields.
    3. Set up abilities that are used to fetch hero descriptions and icons.
    4. Create a trigger in your map that initializes the hero selection.
    5. Set up the interface functions OnEscape and OnFinal.

    ===========================================================================================
    1. Hero Selection Camera

    To set up the hero selection camera, edit
    FOREGROUND_HERO_X
    FOREGROUND_HERO_Y
    HERO_SELECTION_ANGLE

    ===========================================================================================
    2. Importing Heroes

    To import your heroes, go into HeroDeclaration and create a repository for your heroes:
    globals
        Hero MyHero1
        Hero MyHero2
        ...
    endglobals

    In the HeroDeclaration function, initialize the heroes with
        set MyHero1 = Hero.create()
        set MyHero2 = Hero.create()
        ...

    Then, set the fields for your heroes as specified. Some of the variables may already be
    set elsewhere in your map. In that case, I recommend writing a function to automatically
    copy the variables into those fields.

    Note that fields that should be "false", "null", or "0" do not have to be set as they are the
    default values.

    ===========================================================================================
    3. Tooltip abilities

    To set up a tooltip ability, create a new non-hero ability with one level. The tooltip will be
    used to create the hero description.

    ===========================================================================================
    4. Initializing Hero Selection
   
    This system has no init function that runs automatically, since it requires the hero and
    player lists to initialize and you may want to set them at a later point. To initialize, do:

    call InitHeroSelection()

    This will create frames, create the hero list, and initialize players. To start hero selection, do:
   
    call BeginHeroSelection()

    This will enforce the hero selection camera and/or show the menu to players, depending on
    your config. However, players will not be able to select heroes until you do:

    call EnableHeroSelection()

    ===========================================================================================
    5. Creating the Interface

    Now that we have a trigger to begin hero selection, we have to be able to exit it and
    create the heroes that were selected. To do this, edit the OnEscape and OnFinal function
    in the callbacks section. The variables you can use in those functions are "heroIdOfPlayer",
    which stores the unit id of the hero selected, and "heroIconPathOfPlayer", which can be useful
    for updating a multiboard icon, for example.

    ===========================================================================================

    After the hero selection system is successfully integrated into your map, go to the config
    section and adjust the parameters controlling the system's behavior. Then, when everything
    is working as intended, you can go into the fine-tuning section and go nuts customizing
    every part of the system.

    You may want to change some parameters based on the game state. In that case, make the
    parameter public and un-constant it. Note, however, that changing some constants after
    hero selection has been initialized might have no effect and/or lead to bugs.

    ===========================================================================================

    You can customize the appearance of the hero selection menu background by editing the
    HeroSelectionMenu.fdf. By default, the background uses the escape menu textures of whatever
    race the player is playing, but this can be changed by editing BackdropBackground and
    BackdropEdgeFile. Remove DecorateFileNames when using imported assets!

    One important parameter is BackdropCornerSize, which controls the thickness of the border
    texture. Make sure to update MENU_BORDER_TILE_SIZE accordingly, so that the textures remain
    seamless.

    Read more here on how to edit an .fdf file:

    https://www.hiveworkshop.com/threads/ui-reading-a-fdf.315850/

    ===========================================================================================
    Neat Messages:

    The showcase map includes NeatMessages, a library designed to give more control over text
    messages. With default text messages, you will most likely run into the problem that they
    cannot be placed where you want them to and they might hide a UI element or the heroes as a
    result. Therefore, if you want to add text messages to the hero selection, I strongly
    recommend using NeatMessages.

    https://www.hiveworkshop.com/threads/neat-text-messages.350336/

    ===========================================================================================
    How to setup multiple pages:

    Some maps might have more heroes than fit on one page. To setup multiple pages, divide your
    heroes into multiple categories in HeroDeclaration. To hide the category captions, set the
    CATEGORY_NAMES to null in the config under SetCategories. Now, set the PAGE_OF_CATEGORY
    to the page at which the heroes of that category should be appear.

    ===========================================================================================
    How to setup a draft:

    To setup a draft, call BeginHeroSelection, but then, instead of using EnableHeroSelection,
    call EnablePlayerHeroSelection for the player that should pick the first hero. Set a time
    limit for that player picking his or her hero with StartHeroSelectionTimer(timeLimit, yourCallback).
    In your callback, call HeroSelectionPlayerForceRandom. Finally, in the OnPick custom code function,
    write logic to enable hero selection for the next player.

    ===========================================================================================
    Screen coordinates:

    All UI element positions are given in screen coordinates. x-coordinates go from 0 (left) to
    0.8, excluding the widescreen area (no frame can be put there). y-coordinates go from 0 (bottom)
    to 0.6 (top). On a 1080p monitor, 0.00056 corresponds to 1 pixel. 0.01 corresponds to ~18 pixel.
   
    You can place the hero selection menu as well as the mouse-over tooltips into the widescreen
    area by setting the x-position to a negative number / a number greater than 0.8. The width of
    the widescreen area is 0.6*(16/9 - 4/3)/2 = 0.1333, so the lowest possible x-coordinate is
    -0.1333 and the highest 0.9333. The position will automatically be adjusted for players who
    are not using a widescreen monitor.

    Read more on screen coordinates here:
    https://www.hiveworkshop.com/threads/ui-positionate-frames.315860/

    ===========================================================================================
    Page cycle button styles (for PAGE_CYCLE_BUTTON_TEXT_STYLE):

    "EnvelopRandomButton"           Icon-style buttons directly left and right of the random buttons.
    "LeftRightMenuButton"           Icon-style buttons left and right of the menu in the same row as the random buttons.
    "BelowRandomButton"             Icon-style buttons below the random buttons.
    "LeftRightMiddleButton"         Icon-style buttons in the middle of the left and right edges of the menu.
    "RightVerticalButton"           Icon-style buttons in the middle of the right edge of the menu, arranged vertically.
    "EnvelopAcceptButton"           Text-style buttons directly left and right of the accept button.
    "LeftRightBottomButton"         Text-style buttons on the bottom edge in the left and right corners.
    "TangentTopButton"              Text-style buttons in the center of the top edge right next to each other.
    "LeftRightTopButton"            Text-style buttons on the top edge in the left and right corners.
*/

JASS:
library HeroDeclaration

    globals
        Hero MountainKing
        Hero Paladin
        Hero Archmage
        Hero BloodMage
        Hero PriestessOfTheMoon
        Hero DemonHunter
        Hero KeeperOfTheGrove
        Hero Warden
    endglobals
 
    function HeroDeclaration takes nothing returns nothing
        //========================================================================================
        //Here you declare all heroes in your map. The order in which you create them determine the order in which they appear in the hero selection menu.
        //The fields that should be set are:
        /*
        integer array abilities                        The abilities of this hero. Starts at index 1.
        boolean array isNonHeroAbility                Set this flag if the ability at the index is a non-hero ability. This will make the tooltip instead of the research tooltip appear on mouse-over.
        integer unitId                                Unit id of the hero.
        integer tooltipAbility                        Set up any non-hero ability with one level for each hero and write the hero's description in its tooltip.
        string selectEmote                            Sound path of the emote the hero should play when selected.
        animtype selectAnim                         animType of select animation. For example, "spell" is ANIMTYPE_SPELL.
        subanimtype selectSubAnim                   subanimtype of select animation. For example "spell slam" is ANIMTYPE_SPELL + SUBANIMTYPE_SLAM.
        real selectAnimLength                        Length of the select animation. If set incorrectly, animation will be interrupted or freeze.
        integer category                            In which category in the menu should this hero be put?
        boolean needsHeroGlow                        A hero glow will be added to heroes with models that don't have a glow. Requires "GeneralHeroGlow.mdx" to be imported.
        boolean unavailable                            Heroes with this flag will appear in the menu but cannot be picked.
        boolean array unavailableToTeam                Hero will not appear in the menu for players in a team for which this flag was set. Can be used to create completely different hero rosters for different teams.
        */
        //========================================================================================

        set MountainKing                             = Hero.create()
        set Paladin                                 = Hero.create()
        set Archmage                                 = Hero.create()
        set BloodMage                                 = Hero.create()
        set PriestessOfTheMoon                         = Hero.create()
        set DemonHunter                                = Hero.create()
        set KeeperOfTheGrove                        = Hero.create()
        set Warden                                    = Hero.create()

        //========================================================================================

        set Archmage.abilities[1]                     = 'AHbz'
        set Archmage.abilities[2]                     = 'AHwe'
        set Archmage.abilities[3]                     = 'AHab'
        set Archmage.abilities[4]                     = 'AHmt'

        set Archmage.selectEmote                    = "Units\\Human\\HeroArchMage\\HeroArchMageWarcry1.flac"
        set Archmage.unitId                            = 'Hamg'
        set Archmage.tooltipAbility                    = 'Tamg'
        set Archmage.needsHeroGlow                    = false
        set Archmage.category                        = 3
        set Archmage.selectAnim                        = ANIM_TYPE_SPELL
        set Archmage.selectSubAnim                    = null
        set Archmage.selectAnimLength                = 2.7

        //========================================================================================
  
        set Paladin.abilities[1]                     = 'AHhb'
        set Paladin.abilities[2]                     = 'AHds'
        set Paladin.abilities[3]                     = 'AHad'
        set Paladin.abilities[4]                     = 'AHre'

        set Paladin.selectEmote                        = "Units\\Human\\HeroPaladin\\HeroPaladinYesAttack1.flac"
        set Paladin.unitId                            = 'Hpal'
        set Paladin.tooltipAbility                    = 'Tpal'
        set Paladin.needsHeroGlow                    = false
        set Paladin.category                        = 1
        set Paladin.selectAnim                        = ANIM_TYPE_SPELL
        set Paladin.selectSubAnim                    = null
        set Paladin.selectAnimLength                = 2.167
  
        //========================================================================================
  
        set MountainKing.abilities[1]                 = 'AHtb'
        set MountainKing.abilities[2]                 = 'AHtc'
        set MountainKing.abilities[3]                 = 'AHbh'
        set MountainKing.abilities[4]                 = 'AHav'

        set MountainKing.selectEmote                = "Units\\Human\\HeroMountainKing\\HeroMountainKingYesAttack1.flac"
        set MountainKing.unitId                        = 'Hmkg'
        set MountainKing.tooltipAbility                = 'Tmkg'
        set MountainKing.needsHeroGlow                = false
        set MountainKing.category                    = 1
        set MountainKing.selectAnim                    = ANIM_TYPE_SPELL
        set MountainKing.selectSubAnim                = SUBANIM_TYPE_SLAM
        set MountainKing.selectAnimLength            = 1.0
  
        //========================================================================================
  
        set BloodMage.abilities[1]                     = 'AHfs'
        set BloodMage.abilities[2]                     = 'AHbn'
        set BloodMage.abilities[3]                     = 'AHdr'
        set BloodMage.abilities[4]                     = 'AHpx'

        set BloodMage.selectEmote                    = "Units\\Human\\HeroBloodElf\\BloodElfMageYesAttack2.flac"
        set BloodMage.unitId                        = 'Hblm'
        set BloodMage.tooltipAbility                = 'Tblm'
        set BloodMage.needsHeroGlow                    = false
        set BloodMage.category                        = 3
        set BloodMage.selectAnim                    = ANIM_TYPE_SPELL
        set BloodMage.selectSubAnim                    = SUBANIM_TYPE_CHANNEL
        set BloodMage.selectAnimLength                = 2.0
  
        //========================================================================================
  
        set PriestessOfTheMoon.abilities[1]         = 'AEst'
        set PriestessOfTheMoon.abilities[2]         = 'AHfa'
        set PriestessOfTheMoon.abilities[3]         = 'AEar'
        set PriestessOfTheMoon.abilities[4]         = 'AEsf'
  
        set PriestessOfTheMoon.selectEmote            = "Units\\nightElf\\HeroMoonPriestess\\HeroMoonPriestessYesAttack2.flac"
        set PriestessOfTheMoon.unitId                = 'Emoo'
        set PriestessOfTheMoon.tooltipAbility        = 'Tmoo'
        set PriestessOfTheMoon.needsHeroGlow        = false
        set PriestessOfTheMoon.category                = 2
        set PriestessOfTheMoon.selectAnim            = ANIM_TYPE_SPELL
        set PriestessOfTheMoon.selectSubAnim        = null
        set PriestessOfTheMoon.selectAnimLength        = 1.333

        //========================================================================================
  
        set DemonHunter.abilities[1]                 = 'AEmb'
        set DemonHunter.abilities[2]                 = 'AEim'
        set DemonHunter.abilities[3]                 = 'AEev'
        set DemonHunter.abilities[4]                 = 'AEme'
  
        set DemonHunter.selectEmote                    = "Units\\NightElf\\HeroDemonHunter\\HeroDemonHunterYesAttack1.flac"
        set DemonHunter.unitId                        = 'Edem'
        set DemonHunter.tooltipAbility                = 'Tdem'
        set DemonHunter.needsHeroGlow                = false
        set DemonHunter.category                    = 2
        set DemonHunter.selectAnim                    = ANIM_TYPE_SPELL
        set DemonHunter.selectSubAnim                = SUBANIM_TYPE_THROW
        set DemonHunter.selectAnimLength            = 0.9

        //========================================================================================
  
        set KeeperOfTheGrove.abilities[1]             = 'AEer'
        set KeeperOfTheGrove.abilities[2]             = 'AEfn'
        set KeeperOfTheGrove.abilities[3]             = 'AEah'
        set KeeperOfTheGrove.abilities[4]             = 'AEtq'
  
        set KeeperOfTheGrove.selectEmote            = "Units\\NightElf\\HeroKeeperOfTheGrove\\KeeperOfTheGroveYesAttack2.flac"
        set KeeperOfTheGrove.unitId                    = 'Ekee'
        set KeeperOfTheGrove.tooltipAbility            = 'Tkee'
        set KeeperOfTheGrove.needsHeroGlow            = false
        set KeeperOfTheGrove.category                = 3
        set KeeperOfTheGrove.selectAnim                = ANIM_TYPE_STAND
        set KeeperOfTheGrove.selectSubAnim            = SUBANIM_TYPE_VICTORY
        set KeeperOfTheGrove.selectAnimLength        = 3.333

        //========================================================================================
  
        set Warden.abilities[1]                     = 'AEfk'
        set Warden.abilities[2]                     = 'AEbl'
        set Warden.abilities[3]                     = 'AEsh'
        set Warden.abilities[4]                     = 'AEsv'
  
        set Warden.selectEmote                        = "Units\\nightElf\\HeroWarden\\HeroWardenYesAttack3.flac"
        set Warden.unitId                            = 'Ewar'
        set Warden.tooltipAbility                    = 'Twar'
        set Warden.needsHeroGlow                    = false
        set Warden.category                            = 2
        set Warden.selectAnim                        = ANIM_TYPE_SPELL
        set Warden.selectSubAnim                    = null
        set Warden.selectAnimLength                    = 1.2
  
        //========================================================================================

    endfunction

endlibrary

JASS:
library HeroSelectionConfig requires optional NeatMessages

    //=================================================================================================================================
    //These constants determine important aspects of the hero selection. You can fine-tune visuals, sounds etc. further down below.
    //=================================================================================================================================

    globals
        constant boolean HERO_SELECTION_ENABLE_DEBUG_MODE    = true                    //Printout errors and check for function crashes.
   
        //Overall behavior.
        constant boolean HERO_CAN_BE_PICKED_MULTIPLE_TIMES    = false                 //Set to true if the same hero should be able to be picked multiple times.
        constant boolean AUTO_SET_SELECTING_PLAYERS            = false                    //Set to true if hero selection should be enabled for all human players. Set to false if you want to set the player list manually (which can include computer players).
        constant boolean COMPUTER_AUTO_PICK_RANDOM_HERO        = false                    //Set to true if computer players that are in hero selection should automatically pick a random hero after all human players have picked.
        constant boolean ESCAPE_PLAYER_AFTER_SELECTING        = false                 //Set to true if a player picking a hero should kick him or her out of the hero selection menu and camera.
        constant boolean CONCEAL_HERO_PICKS_FROM_ENEMIES    = false                 //Set to true if hero picks should be concealed from enemies (players in another team or all players if there are no teams), including emote, effect, and text messages.
        constant boolean MENU_INCLUDE_RANDOM_PICK            = true                    //Include button for picking a random hero at the bottom of the menu.
        constant boolean MENU_INCLUDE_SUGGEST_RANDOM        = true                    //Include button to pre-select a random hero next to the random pick button.
        constant real TIME_LIMIT                            = 60                    //Set a time limit after which players who haven't chosen a hero will be assigned one at random. Set to 0 for no time limit.

        //Camera and foreground hero positions (background hero locations are set in GetPlayerBackgroundHeroLocation).
        constant boolean SEPARATE_LOCATIONS_FOR_EACH_TEAM    = false                    //Set to true if each team gets a different selection screen (all following values will be ignored and you have to set the array equivalents in InitArrays).
        constant real FOREGROUND_HERO_X                        = -128                    //x-Position of foreground hero.
        constant real FOREGROUND_HERO_Y                        = 256                    //y-Position of foreground hero.
        constant real HERO_SELECTION_ANGLE                    = 0                        //Hero selection viewing direction of camera (0 = facing east, 90 = facing north etc.).

        //Visuals.
        constant boolean ENFORCE_CAMERA                        = true                    //Set to false if players' camera should not be changed during hero selection.
        constant boolean HIDE_GAME_UI                       = true                  //Set to true if all UI elements other than the hero selection menu should be hidden.
        constant boolean CREATE_FOREGROUND_HERO                = true                    //Set to false if no foreground hero should appear on preselection.
        constant boolean CREATE_BACKGROUND_HEROES              = true                  //Set to true if players should be able to see other players' picks in the background.
    endglobals

    //=================================================================================================================================

    globals
        string array CATEGORY_NAMES                            //Names of the hero categories that appear in the hero selection menu.
        integer array PAGE_OF_CATEGORY                        //Set the page at which that category appears within the menu. When there is more than one page, page cycle buttons appear in the menu.
        boolean array PLAYER_SELECTS_HERO                    //List of all players that are supposed to participate in hero selection (true = participates).
        integer array TEAM_OF_PLAYER                        //Team of player, 1, 2 etc. 0 = no teams.
        string array PLAYER_COLOR                            //Hex color string (including "|cff") for each player.
    endglobals

    function SetCategories takes nothing returns nothing
        //Set the names of the categories in the hero selection menu. Starts at index 1. Set to null for no category caption.
        set CATEGORY_NAMES[1]                        = "|cffffcc00Strength|r"
        set CATEGORY_NAMES[2]                        = "|cffffcc00Agility|r"
        set CATEGORY_NAMES[3]                        = "|cffffcc00Intelligence|r"

        //Set the pages of the categories in the hero selection menu. Pages start at 1.
        set PAGE_OF_CATEGORY[1]                        = 1
        set PAGE_OF_CATEGORY[2]                        = 1
        set PAGE_OF_CATEGORY[3]                        = 1
    endfunction

    function HeroSelectionInitPlayers takes nothing returns nothing   
        //Set the list of players for which hero selection is enabled (only if AUTO_SET_SELECTING_PLAYERS = false). It is disabled here because computer players must be added to the hero selection.
        set PLAYER_SELECTS_HERO[0]                    = true
        set PLAYER_SELECTS_HERO[1]                    = true
        set PLAYER_SELECTS_HERO[2]                    = true
        set PLAYER_SELECTS_HERO[3]                    = true
        set PLAYER_SELECTS_HERO[4]                    = true
        set PLAYER_SELECTS_HERO[5]                    = true
   
        //Set the teams of players. Players with team 0 are enemies to all players.
        set TEAM_OF_PLAYER[0]                        = 1
        set TEAM_OF_PLAYER[1]                        = 1
        set TEAM_OF_PLAYER[2]                        = 1
        set TEAM_OF_PLAYER[3]                        = 2
        set TEAM_OF_PLAYER[4]                        = 2
        set TEAM_OF_PLAYER[5]                        = 2
    endfunction

    //=================================================================================================================================

    globals
        real array TEAM_FOREGROUND_HERO_X                    //x-Position of foreground hero.
        real array TEAM_FOREGROUND_HERO_Y                    //y-Position of foreground hero.
        real array TEAM_HERO_SELECTION_ANGLE                //Hero selection camera yaw in degrees (0 = facing east, 90 = facing north etc.).
    endglobals

    function HeroSelectionInitArrays takes nothing returns nothing
        //If you set SEPARATE_LOCATIONS_FOR_EACH_TEAM = true, initialize the above array variables here.
    endfunction
   
    function GetPlayerBackgroundHeroLocation takes integer playerIndex, integer whichSlot, integer numberOfPlayers, integer whichTeam, integer slotInTeam, integer teamSize returns location
        local real deltaHorizontal
        local real deltaVertical

        //=================================================================================================================================
        /*
        CREATE_BACKGROUND_HEROES only.
        Here you can customize the positions of the background heroes. Init loops through and calls this function for each player.

        Input:
        playerIndex
        whichSlot                 The position of the player when enumerating all players for which hero selection is enabled.
        numberOfPlayers         The number of players for which hero selection is enabled.
        whichTeam                 The team the player is assigned to. Discard if there are no teams.
        slotInTeam                 The position of that player within his or her team. Discard if there are no teams.
        teamSize                 The size of the player's team. Discard if there are no teams.

        Output:
        deltaVertical            The distance between the background hero and the camera eye position along the camera viewing direction.
        deltaHorizontal            The offset between the background hero and the camera eye position perpendicular to the camera viewing direction.
        */
        //==================================================================================================================================

        //Example code:
        local real angle
        local real dist
        local real BACKGROUND_HERO_OFFSET_BACKROW = 1425
        local real BACKGROUND_HERO_OFFSET_FRONTROW = 1250

        if teamSize == 1 then
            set angle = 9
            set dist = BACKGROUND_HERO_OFFSET_BACKROW
        elseif slotInTeam == 1 then
            set angle = 5
            set dist = BACKGROUND_HERO_OFFSET_BACKROW
        elseif slotInTeam == 2 then
            set angle = 13
            set dist = BACKGROUND_HERO_OFFSET_BACKROW
        else
            set angle = 9
            set dist = BACKGROUND_HERO_OFFSET_FRONTROW
        endif
       
        if whichTeam == 2 then
            set angle = -angle
        endif

        set deltaHorizontal = Sin(Deg2Rad(angle))*dist
        set deltaVertical = Cos(Deg2Rad(angle))*dist

        //==================================================================================================================================

        return Location(deltaHorizontal, deltaVertical)
    endfunction



    //==================================================================================================================================
    //                                                    F I N E - T U N I N G
    //==================================================================================================================================



    globals
        //How hero selection looks like before it is enabled (you can ignore this if you plan on enabling hero selection right away).
        constant boolean SHOW_MENU_BEFORE_ENABLED            = false                    //Set to true if players should be able to see the hero selection menu before hero selection is enabled.
        constant boolean PRE_SELECT_BEFORE_ENABLED          = false                 //Set to true if players should be able to pre-select heroes before hero selection is enabled.
       
        //Camera setup (ENFORCE_CAMERA).
        constant real CAMERA_PITCH                            = 12                    //Hero selection camera angle of attack in degrees. 0 = facing horizon, 90 = facing ground.
        constant real CAMERA_DISTANCE                        = 1300                    //Hero selection camera distance from camera target.
        constant real CAMERA_TARGET_OFFSET                    = 500                    //Distance between foreground hero and camera target. Positive = Camera target is behind the hero.
        constant real CAMERA_PERPENDICULAR_OFFSET            = 0                        //Shifts the camera left (negative) or right (positive) with respect to the foreground hero.
        constant real CAMERA_Z_OFFSET                        = 100                    //Hero selection camera z-offset.
        constant real CAMERA_FIELD_OF_VIEW                    = 70                    //Hero selection camera field of view.

        //Sounds and visuals of hero pre-selection (CREATE_FOREGROUND_HERO).
        constant string PRESELECT_EFFECT                    = null                    //Special effect played on the hero's position for the triggering player when switching to a new hero during pre-selection. Set to null for no effect.
        constant boolean PLAY_EMOTE_ON_PRESELECT            = true                  //Set to true if the hero should play its selection emote when the player switches to that hero during pre-selection.
        constant boolean PLAY_ANIMATION_ON_PRESELECT        = true                  //Set to true if the hero should play the selection animation when the player switches to that hero during pre-selection.
        constant boolean PHANTOM_HERO_WHEN_CANNOT_BE_PICKED    = true                    //Set to true if a hero should be black and transparent when it is pre-selected but cannot be picked.
        constant real FOREGROUND_HERO_Z                        = 0                        //z-Position of foreground hero.

        //Sounds and visuals on hero pick.
        constant string PICK_EFFECT                            = "HolyLight.mdx"        //Special effect played on the hero's position for the triggering player when picking a hero. Set to null for no effect.
        constant string PICK_SOUND                            = "Sound\\Interface\\ItemReceived.flac"    //Sound effect played for the triggering player when selecting a hero. Set to null for no sound.
        constant boolean PLAY_EMOTE_ON_PICK                    = false                 //Set to true if a hero should play its selection emote when a player chooses that hero.
        constant boolean PLAY_ANIMATION_ON_PICK                = false                 //Set to true if a hero should play the selection animation when a player chooses that hero.
        constant real PLAYER_PICK_ESCAPE_DELAY                = 4.0                    //Delay between selecting a hero and being kicked out of hero selection (ESCAPE_PLAYER_AFTER_SELECTING).
        constant real FOREGROUND_HERO_FADEOUT_DELAY            = 1.0                   //The time it takes for the foreground hero to start fading out after being selected.
        constant real FOREGROUND_HERO_FADEOUT_TIME            = 1.5                   //The time it takes for the foreground hero to fade out after being selected.
        constant string OTHER_PLAYER_HERO_PICK_SOUND        = "Sound\\Interface\\InGameChatWhat1.flac"    //Sound played when another player picks a hero. Set to null for no sound.
       
        //Text messages on hero pick.
        constant boolean CREATE_TEXT_MESSAGE_ON_PICK        = true                    //Set to true if a text message should be sent to all other players when a hero is picked (except to enemies when concealed).
        constant real TEXT_MESSAGE_X_OFFSET                    = 0.35                    //x-Offset of text messages from default. Text messages will still suck. Recommend using NeatMessages.
        constant real TEXT_MESSAGE_Y_OFFSET                    = 0                        //y-Offset of text messages from default.
        constant boolean USE_HERO_PROPER_NAME                = false                    //Instead of the hero's name, its proper name will be displayed in the text message. For example, "Uther" instead of "Paladin". Will temporarily create a hero to get the proper name.
        constant boolean MESSAGE_EVEN_WHEN_CONCEALED        = true                    //Set to true if players should still get a message notifying that a player has picked a hero even when it is concealed which hero was picked (CONCEAL_HERO_PICKS_FROM_ENEMIES).
        constant boolean INCLUDE_PROGRESSION_IN_MESSAGE        = false                 //Set to true if the displayed text message should include how many players have selected their hero.

        //Sounds and visuals of background heroes (CREATE_BACKGROUND_HEROES).
        constant boolean PLAYER_TEXT_TAGS                   = true                  //Create text tags that show the players' names over the background heroes.
        constant real BACKGROUND_HERO_FADEIN_TIME            = 1.0                   //The time it takes for the background hero to fade in after being selected.
        constant boolean PLAY_EMOTE_ON_BACKGROUND_HERO      = true                     //Set to true if a hero should play its selection emote for all other players as it fades in.
        constant boolean PLAY_ANIMATION_ON_BACKGROUND_HERO  = true                  //Set to true if the background hero should play its selection animation as it fades in.
        constant string BACKGROUND_HERO_FADEIN_EFFECT       = "HolyLightRoyal.mdx"  //Special effect played on the background hero as it fades in.
        constant string BACKGROUND_HERO_SELF_HIGHLIGHT        = "RadianceHoly.mdx"    //Special effect added at the location of a player's own background hero to highlight it. Set to null for no highlight.
        constant real BACKGROUND_HERO_HIGHLIGHT_Z            = 70                    //z-Position of background hero self highlight.
        constant real BACKGROUND_HERO_FACING_POINT_OFFSET    = -500                    //Adjusts where the background heroes face. 0 = Background heroes will face the foreground hero. Negative value = Face a point closer to the camera. Positive value = Face a point further from the camera.
        constant string CONCEALED_HERO_EFFECT                = "Objects\\InventoryItems\\QuestionMark\\QuestionMark.mdl"    //Model for the background hero seen by a player for which the hero pick was concealed.

        //Last player picks a hero.
        constant real LAST_PLAYER_SELECT_END_DELAY            = 6.0                    //The amount of time after the last player selects a hero and the hero selection ends (ignore if ESCAPE_PLAYER_AFTER_SELECTING)
        constant boolean DELETE_BACKGROUND_HEROES_AFTER_END    = false                    //Set to false if background heroes should not get removed when hero selection ends. For example, when a player repicks, they are still there.

        //Layout of hero selection menu.
        constant integer MENU_NUMBER_OF_COLUMNS                = 2                        //Number of hero buttons per row.
        constant real MENU_X_LEFT                            = 0                        //x-Position of left edge of hero selection menu.
        constant real MENU_Y_TOP                            = 0.55                    //y-Position of top edge of hero selection menu.
        constant real MENU_BUTTON_SIZE                        = 0.039                    //Size of individual hero buttons.
        constant real MENU_LEFT_RIGHT_EDGE_GAP                = 0.02                    //Gap between left and right edges of menu and first and last buttons.
        constant real MENU_TOP_EDGE_GAP                        = 0.015                    //Gap between top edge of menu and first button/first category title.
        constant real MENU_BOTTOM_EDGE_GAP                    = 0.02                    //Gap between bottom edge of menu and last button.
        constant real MENU_BUTTON_BUTTON_GAP                = 0.005                    //Gap between two individual hero buttons.
        constant real MENU_CATEGORY_FONT_SIZE                = 16                    //Font size of category titles.
        constant real MENU_CATEGORY_GAP                        = 0.028                    //Gap between buttons of two different categories.
        constant real MENU_CATEGORY_TITLE_Y                    = -0.001                //y-Position shift between category title and center of gap between categories.
        constant real MENU_BORDER_TILE_SIZE                    = 0.03                    //This rounds up the width and height of the menu to an integer multiple of the specified number. This is useful if you're using a tiled border texture, so that there's no discontinuity in the texture.
                                                                                    //Value should be equal to BackdropCornerSize in the HeroSelectionMenu.fdf. Set to 0 to disable.
        constant real MENU_HEROES_RANDOM_GAP                = 0.02                    //Additional gap between last hero button and random pick button.
       
        //Select button.
        constant string SELECT_BUTTON_TEXT                    = "Accept"                //The text in the select hero button at the bottom of the menu.
        constant real SELECT_BUTTON_SCALE                    = 1.0                    //The scale of the select hero button at the bottom of the menu.
        constant real SELECT_BUTTON_WIDTH                    = 0.092                    //The width of the select hero button at the bottom of the menu.

        //Display of random options.
        constant string RANDOM_HERO_TOOLTIP                    = "Choose a random hero."
        constant string SUGGEST_RANDOM_TOOLTIP                = "Suggest a random hero, but don't select it just yet."
        constant string RANDOM_HERO_ICON                    = "ReplaceableTextures\\CommandButtons\\BTNRandomIncredibleIcon.blp"
        constant string SUGGEST_RANDOM_ICON                    = "ReplaceableTextures\\CommandButtons\\BTNSelectHeroOn.blp"
        constant boolean RANDOM_SELECT_CYCLE_STYLE            = true                    //Set to true if the foreground hero should cycle randomly between different heroes while random hero is pre-selected. Set to false if a question mark should be shown.
        constant real RANDOM_SELECT_CYCLE_INTERVAL            = 0.2                    //How fast the heroes are cycled through when random hero is pre-selected (RANDOM_SELECT_CYCLE_STYLE).
       
        //Layout of ability preview buttons.
        constant real HERO_ABILITY_PREVIEW_BUTTON_X            = 0.0                    //x-Position of topmost ability preview button relative to topright corner of menu.
        constant real HERO_ABILITY_PREVIEW_BUTTON_Y            = -0.016                //y-Position of topmost ability preview button relative to topright corner of menu.
        constant real HERO_ABILITY_PREVIEW_BUTTON_SIZE        = 0.025                    //The size of the hero ability preview buttons that appear on the right side of the menu when pre-selecting a hero.
        constant boolean ABILITY_BUTTON_HORIZONTAL_LAYOUT    = false                    //Set to true if ability preview buttons should be arranged horizontally instead of vertically.
        constant string HERO_ABILITY_LEVEL_DELIMITER        = " - ["                //To get the name of a hero ability without the level-text from the tooltip (such as "Stormbolt - [Level 1]" -> "Stormbolt"). This string is used to detect where the name of the ability ends. Change if ability names in tooltips are not correct. Set to null if not necessary.

        //Page cycle buttons (when multiple pages are set).
        constant string PAGE_CYCLE_BUTTON_STYLE                = "BelowRandomButton"    //Set the page cycle button style. Styles are found in documentation.
        constant real MENU_PAGE_CYCLE_X_OFFSET                = 0.0                    //x-Position of the page cycle buttons relative to the auto-position. Positive = moves inward. Negative = moves outward.
        constant real MENU_PAGE_CYCLE_Y_OFFSET                = 0.0                    //y-Position of the page cycle up button relative to the auto-position.
        constant real MENU_PAGE_CYCLE_SCALE                    = 1.0                    //Sets the size of the page cycle buttons relative to hero buttons (for icon style buttons) or the accept button (for text style buttons).
        constant string MENU_PAGE_DOWN_ICON                    = "ReplaceableTextures\\CommandButtons\\BTNNagaBurrow.blp"        //(for icon style buttons)
        constant string MENU_PAGE_UP_ICON                    = "ReplaceableTextures\\CommandButtons\\BTNNagaUnBurrow.blp"    //(for icon style buttons)

        //Display of big caption.
        constant string HERO_SELECTION_CAPTION                = "Choose your Hero!"    //The text displayed on the screen during hero selection. Set null to omit text.
        constant real CAPTION_FONT_SIZE                        = 30                    //Font size of hero selection caption.
        constant real CAPTION_X                                = 0.4                    //x-Position of caption center.
        constant real CAPTION_Y                                = 0.41                    //y-Position of caption center.
        constant string CAPTION_COLOR_1                        = "|cffffcc00"            //Caption will cycle between color 1 and color 2. Set the same for no color cycling.
        constant string CAPTION_COLOR_2                        = "|cffffffff"            //Caption will cycle between color 1 and color 2. Set the same for no color cycling.
        constant integer CAPTION_ALPHA_1                    = 255                    //Caption will cycle between alpha 1 and alpha 2. Set the same for no alpha cycling.
        constant integer CAPTION_ALPHA_2                    = 255                    //Caption will cycle between alpha 1 and alpha 2. Set the same for no alpha cycling.
        constant real CAPTION_CYCLE_TIME                    = 4.0                    //The time it takes for the caption to cycle between color 1/2 and alpha 1/2.
        constant real CAPTION_FADEOUT_TIME                    = 2.0                    //The time it takes for the caption to fade out after a player has picked a hero. Set to -1 for no fade out.
   
        //Position of hero button and ability preview button mouse-over tooltips.
        constant real TOOLTIP_LEFT_X                        = 0.51
        constant real TOOLTIP_Y                              = 0.13
        constant boolean TOOLTIP_LOCK_TOP                    = false                    //Set to true if TOOLTIP_Y should refer to top instead of bottom edge.
        constant real TOOLTIP_WIDTH                         = 0.29

        //Display of countdown timer.
        constant string TIMER_TEXT                            = "Time Remaining: |cffffcc00"    //Text before the time remaining display
        constant string TIMER_BAN_PHASE_TEXT                = "Ban Phase: |cffffcc00"        //Text before the time remaining display during the ban phase.
        constant real TIMER_FONT_SIZE                        = 11                    //Font size of the time remaining display.
        constant real TIMER_X                                = 0.4                    //x-Position of center of the time remaining display.
        constant real TIMER_Y                                = 0.59                    //y-Position of the center of the time remaining display.

        //Shadows of heroes.
        constant boolean CREATE_SHADOWS                        = false                    //Create shadows with destructables for heroes (since they are special effects and don't have shadows). Import destructables from showcase map to enable.
        constant integer SHADOW_DESTRUCTABLE_ID                = 'Dsha'                //Destructable id of the shadow that's created for heroes.
        constant integer NO_SHADOW_DESTRUCTABLE_ID            = 'Dnsh'                //Dummy destructable without a shadow.
    endglobals

    //=================================================================================================================================

    function HeroSelectionSetPlayerColors takes nothing returns nothing
        //Set the colors of players shown for their names in text messages.
        set PLAYER_COLOR[0]                         = "|cffff0402"
        set PLAYER_COLOR[1]                         = "|cff1052ff"
        set PLAYER_COLOR[2]                         = "|cff1BE6BA"
        set PLAYER_COLOR[3]                         = "|cff8530b1"
        set PLAYER_COLOR[4]                         = "|cfffffc00"
        set PLAYER_COLOR[5]                         = "|cffff8a0d"
        set PLAYER_COLOR[6]                         = "|cff20bf00"
        set PLAYER_COLOR[7]                            = "|cffE35BAF"
        set PLAYER_COLOR[8]                            = "|cff949697"
        set PLAYER_COLOR[9]                         = "|cff7EBFF1"
        set PLAYER_COLOR[10]                         = "|cff106247"
        set PLAYER_COLOR[11]                         = "|cff4F2B05"
        set PLAYER_COLOR[12]                         = "|cff9C0000"
        set PLAYER_COLOR[13]                         = "|cff0000C2"
        set PLAYER_COLOR[14]                         = "|cff00EBEB"
        set PLAYER_COLOR[15]                        = "|cffBE00FF"
        set PLAYER_COLOR[16]                        = "|cffECCC86"
        set PLAYER_COLOR[17]                         = "|cffF7A48B"
        set PLAYER_COLOR[18]                         = "|cffBFFF80"
        set PLAYER_COLOR[19]                         = "|cffDBB8EC"
        set PLAYER_COLOR[20]                         = "|cff4F4F55"
        set PLAYER_COLOR[21]                         = "|cffECF0FF"
        set PLAYER_COLOR[22]                         = "|cff00781E"
        set PLAYER_COLOR[23]                        = "|cffA46F34"
    endfunction

endlibrary

JASS:
library HeroSelectionCallbacks requires HeroSelection

	globals

		//=============================================================================================================================
		//Globals for interfacing with rest of map. All player indices start at 0.
		//=============================================================================================================================

		integer array heroIdOfPlayer								//Stores the unit id of the hero each player picked.
		string array heroIconPathOfPlayer							//Stores the icon path of the hero each player picked.
		
		//=============================================================================================================================
		//Can also be used in custom code. Read-Only!

		boolean array playerHasHero									//Player has picked a hero (does not mean hero was created).
		boolean array isInHeroSelection								//Player is currently viewing the hero selection screen.
	
		boolean inBanPhase											= false
		boolean array playerIsHuman
		string array coloredPlayerName
		boolean array heroSelectionDisabledForPlayer
		boolean array heroPreselectionDisabledForPlayer
		boolean array playerHasBan
		boolean array isRepicking
		integer numPlayersWithHero 									= 0
		integer numPlayersInSelection 								= 0
		integer numSelectingPlayers 								= 0
		integer numSelectingHumans									= 0
		integer array playerNumberOfSlot							//To get the player index when iterating over all selecting players from 1 to numSelectingPlayers.
		
		effect array selectedHero
		effect array backgroundHeroHighlight
		effect array backgroundHero

		real array BACKGROUND_HERO_X
		real array BACKGROUND_HERO_Y
		integer NUMBER_OF_HEROES									= 0

	endglobals

	//=================================================================================================================================
	//These functions are the main interface with the rest of your map.
	//=================================================================================================================================

	function HeroSelectionOnEscape takes player whichPlayer returns nothing
		//Here you can insert code that is executed when a player is kicked out of hero selection. This should include creating
		//the hero that player selected as well as setting that player's camera location. Will be executed before OnFinal.

		//Example code:
		static if LIBRARY_TestHeroSelection then
			set playerHero[GetPlayerId(whichPlayer)] = CreateUnit(whichPlayer, heroIdOfPlayer[GetPlayerId(whichPlayer)], HERO_SPAWN_X, HERO_SPAWN_Y, 90)
			if GetLocalPlayer() == whichPlayer then
				call SetCameraPosition(HERO_SPAWN_X, HERO_SPAWN_Y)
			endif
			static if LIBRARY_NeatMessages then
				call ClearNeatMessages()
			else
				call ClearTextMessages()
			endif
		endif
	endfunction
	
	function HeroSelectionOnFinal takes nothing returns nothing
		//Here you can insert the code that is executed when hero selection is concluded. This will most likely involve calling the main
		//function that progresses your map after hero selection ends. It is recommended to execute player-specific actions in OnEscape
		//instead.

		//Example code:
		static if LIBRARY_TestHeroSelection then
			call ExecuteFunc("BeginGame")
		endif
	endfunction
	
	//=================================================================================================================================
	//These functions allow you to add additional visual effects, sounds, texts etc. to the hero selection that can't be achieved with 
	//the default options.
	//=================================================================================================================================
	
    function HeroSelectionOnRestart takes nothing returns nothing
        //Here you can insert additional code that is executed when hero selection is restarted.
        //Example code:
        static if LIBRARY_TestHeroSelection then
            local integer i = 1
            local integer P
            loop
                exitwhen i > numSelectingPlayers
                set P = playerNumberOfSlot[i]
                call DestroyEffect(circles[P])
                set i = i + 1
            endloop
        endif
    endfunction

	function HeroSelectionOnLast takes nothing returns nothing
		//Here you can insert additional code that is executed when the last player picks a hero.
		//Example code:
		static if LIBRARY_TestHeroSelection then
			call ExecuteFunc("MusicSlowFadeOut")
		endif
	endfunction
	
	function HeroSelectionOnPick takes player whichPlayer, integer whichHero, boolean wasRandomPick, boolean wasRepick returns nothing
		//Here you can insert additional code that is executed when a player picks a hero.
	endfunction

	function HeroSelectionOnPreselect takes player whichPlayer, Hero oldHero, Hero newHero returns nothing
		//Here you can insert additional code that is executed when a player switches to a new hero during pre-selection.
	endfunction

	function HeroSelectionOnReturn takes player whichPlayer returns nothing
		//Here you can insert additional code that is executed when a player returns to hero selection.
	endfunction

	function HeroSelectionOnAllRandom takes nothing returns nothing
		//Here you can insert additional code that is executed when all random mode is selected.
	endfunction

	function HeroSelectionOnBegin takes nothing returns nothing
		//Here you can insert additional code that is executed when players are locked into hero selection.
		//Example code:
		static if LIBRARY_NeatMessages and LIBRARY_TestHeroSelection then
			call NeatMessageTimedInWindow(5, "Heroes from all across Azeroth have gathered to fight the darkness. Only you can save this land...", centerWindow)
		endif
	endfunction
	
	function HeroSelectionOnEnable takes nothing returns nothing
		//Here you can insert additional code that is executed when hero selection is enabled.
		//Example code:
		local integer i = 1
		local integer P
		local location tempLoc
		static if LIBRARY_TestHeroSelection then
			loop
				exitwhen i > numSelectingPlayers
				set P = playerNumberOfSlot[i]
				set circles[P] = AddSpecialEffect("buildings\\other\\CircleOfPower\\CircleOfPower.mdl", BACKGROUND_HERO_X[P], BACKGROUND_HERO_Y[P])
				call BlzSetSpecialEffectColorByPlayer(circles[P], Player(PLAYER_NEUTRAL_AGGRESSIVE))
				set tempLoc = Location(BACKGROUND_HERO_X[P], BACKGROUND_HERO_Y[P])
				call BlzSetSpecialEffectZ(circles[P], GetLocationZ(tempLoc) + 10)
				call RemoveLocation(tempLoc)
				set i = i + 1
			endloop
		endif
		call ClearMapMusic()
		call PlayMusic("Nightsong.mp3")
	endfunction

	function HeroSelectionOnExpire takes nothing returns nothing
		//Here you can insert the code that is executed when the timer for the hero selection duration expires.
		//Example code:
		static if LIBRARY_NeatMessages and LIBRARY_TestHeroSelection then
			call NeatMessageTimedInWindow(4, "The time has expired.", centerWindow)
		endif
	endfunction

	function HeroSelectionOnLeave takes player whichPlayer returns nothing
		//Here you can insert additional code that is executed when a player who is in hero selection leaves the game.
	endfunction

endlibrary

JASS:
library HeroSelection requires HeroSelectionConfig, HeroDeclaration, optional NeatMessages

	globals
		private effect array selectedHeroGlow
		private effect array backgroundHeroGlow
		private destructable array backgroundHeroShadow
		private destructable foregroundHeroShadow					= null
        private texttag array backgroundHeroTextTag

		private integer array preselectedHeroIndex
		private integer array pickedHeroIndex
		private integer localPlayerId												//asynchronous
		private boolean array heroIndexWasPicked
		private boolean array heroIndexWasBanned
		private real localForegroundHeroX											//asynchronous
		private real localForegroundHeroY											//asynchronous
		private real localHeroSelectionAngle										//asynchronous
		private integer currentPage									= 1				//asynchronous
	
		private framehandle captionFrame 							= null
		private framehandle timerFrame 								= null
		private framehandle heroSelectionMenu						= null
		private framehandle array heroSelectionButton
		private framehandle array heroSelectionButtonIcon
		private framehandle heroSelectionButtonHighlight
		private framehandle array heroSelectionButtonIconClicked
		private framehandle array heroSelectionButtonTooltip
		private framehandle array heroSelectionButtonTooltipTitle
		private framehandle array heroSelectionButtonTooltipText
		private framehandle array heroSelectionAbility
		private framehandle array heroSelectionAbilityHover
		private framehandle array heroSelectionAbilityTooltip
		private framehandle array heroSelectionAbilityTooltipTitle
		private framehandle array heroSelectionAbilityTooltipText
		private framehandle array heroSelectionCategory
		private framehandle heroAcceptButton						= null
		private framehandle heroBanButton							= null
		
		private camerasetup heroSelectionCamera 					= CreateCameraSetup()
		private hashtable hash 										= InitHashtable()
		private timer lockCameraTimer 								= CreateTimer()
		private timer captionTimer 									= CreateTimer()
		private timer countdownTimer								= CreateTimer()
		private timer countdownUpdateTimer							= CreateTimer()
		private trigger heroSelectionButtonTrigger
		private trigger pageCycleTrigger
		
		private framehandle fullScreenFrame
		private framehandle fullScreenParent
		
		private boolean isForcedSelect 								= false
		private real captionAlphaMultiplier							= 1
		
		private constant real TOOLTIP_BASE_HEIGHT					= 0.032
        private real tooltipLeftXLocal
		
		private constant string HEX_STRING							= "0123456789abcdef"
		private integer r1
		private integer r2
		private integer g1
		private integer g2
		private integer b1
		private integer b2
		
		private integer storePlayerIndex
		private integer storeHeroIndex

		private real GARBAGE_DUMP_X
		private real GARBAGE_DUMP_Y
		
		private integer NUMBER_OF_CATEGORIES						= 0
		private integer NUMBER_OF_PAGES								= 1
		private integer NUMBER_OF_ABILITY_FRAMES					= 0
		private integer NUMBER_OF_TEAMS								= 0
		private integer RANDOM_HERO									= 0
		private integer SUGGEST_RANDOM								= 0
		private integer PAGE_DOWN									= 0
		private integer PAGE_UP										= 0
	endglobals
	
	private function interface playerCallback takes player whichPlayer returns nothing
	private function interface playerHeroCallback takes player whichPlayer, Hero whichHero returns nothing
	private function interface onPickCallback takes player whichPlayer, Hero whichHero, boolean wasRandom, boolean wasRepick returns nothing
	private function interface playerHeroHeroCallback takes player whichPlayer, Hero oldHero, Hero newHero returns nothing
	private function interface noArgCallback takes nothing returns nothing

	struct Hero
		//Fields that must be set.
		//===================================
		integer array abilities[9]
		boolean array isNonHeroAbility[9]
		integer unitId
		integer tooltipAbility
		string selectEmote
		animtype selectAnim
		subanimtype selectSubAnim
		real selectAnimLength
		integer category
		boolean needsHeroGlow
		boolean unavailable
		boolean array unavailableToTeam[5]
		//===================================

		readonly string modelPath
		readonly integer index
		readonly string iconPath
		readonly real scalingValue
		readonly string name
		readonly integer red
		readonly integer green
		readonly integer blue

		readonly static integer numHeroes = 0
		readonly static Hero array list

		static method create takes nothing returns Hero
			local Hero this = Hero.allocate()
			set numHeroes = numHeroes + 1
			set .index = numHeroes
			set list[numHeroes] = this
			return this
		endmethod

		method GetValues takes nothing returns nothing
			local unit tempUnit
			local item tempItem

			set tempUnit = CreateUnit( Player(PLAYER_NEUTRAL_PASSIVE) , .unitId , GARBAGE_DUMP_X , GARBAGE_DUMP_Y , 0 )
			set .name = GetUnitName(tempUnit)
			set .scalingValue = BlzGetUnitRealField( tempUnit , UNIT_RF_SCALING_VALUE )
			set .red = BlzGetUnitIntegerField(tempUnit,  UNIT_IF_TINTING_COLOR_RED)
			set .green = BlzGetUnitIntegerField(tempUnit,  UNIT_IF_TINTING_COLOR_GREEN)
			set .blue = BlzGetUnitIntegerField(tempUnit,  UNIT_IF_TINTING_COLOR_BLUE)
			set .iconPath = BlzGetAbilityIcon(GetUnitTypeId(tempUnit))
			call RemoveUnit(tempUnit)
			set tempUnit = null

			set tempItem = CreateItem('afac', GARBAGE_DUMP_X, GARBAGE_DUMP_Y)
			call BlzSetItemSkin(tempItem, .unitId)
			set .modelPath = BlzGetItemStringField(tempItem, ITEM_SF_MODEL_USED)
			call RemoveItem(tempItem)
			set tempItem = null
		endmethod
	endstruct
	
	//==========================================================================================================================================================
	//Utility
	//==========================================================================================================================================================

    private function InitFullScreenParents takes nothing returns nothing
        local framehandle board
        call CreateLeaderboardBJ(bj_FORCE_ALL_PLAYERS, "title")
        set board = BlzGetFrameByName("Leaderboard", 0)
        call BlzFrameSetSize(board, 0, 0)
        call BlzFrameSetVisible(BlzGetFrameByName("LeaderboardBackdrop", 0), false)
        call BlzFrameSetVisible(BlzGetFrameByName("LeaderboardTitle", 0), false)
        set fullScreenParent = BlzCreateFrameByType("FRAME", "fullScreenParent", board, "", 0)
        set fullScreenFrame = BlzCreateFrameByType("FRAME", "fullscreen", fullScreenParent, "", 0)
        call BlzFrameSetVisible(fullScreenFrame, false)
        call BlzFrameSetSize(fullScreenFrame, BlzGetLocalClientWidth()/I2R(BlzGetLocalClientHeight())*0.6, 0.6)
        call BlzFrameSetAbsPoint(fullScreenFrame, FRAMEPOINT_BOTTOM, 0.4, 0)
    endfunction

	private function CheckForCrash takes nothing returns nothing
		static if HERO_SELECTION_ENABLE_DEBUG_MODE then
			local timer crashTimer = GetExpiredTimer()
			local boolean didCrash = LoadBoolean(hash, GetHandleId(crashTimer), 0)
			local string functionName
			if didCrash then
				set functionName = LoadStr(hash, GetHandleId(crashTimer), 1)
				call BJDebugMsg("|cffff0000Warning:|r " + functionName + " function crashed...")
			endif
			call FlushChildHashtable(hash, GetHandleId(crashTimer))
			call DestroyTimer(crashTimer)
			set crashTimer = null
		endif
	endfunction

	private function InitCrashCheck takes string functionName returns nothing
		static if HERO_SELECTION_ENABLE_DEBUG_MODE then
			local timer crashTimer = CreateTimer()
			call TimerStart(crashTimer, 0.01, false, function CheckForCrash)
			call SaveBoolean(hash, GetHandleId(crashTimer), 0, true)
			call SaveStr(hash, GetHandleId(crashTimer), 1, functionName)
			call SaveTimerHandle(hash, StringHash(functionName), 0, crashTimer)
			set crashTimer = null
		endif
	endfunction

	private function NoCrash takes string functionName returns nothing
		static if HERO_SELECTION_ENABLE_DEBUG_MODE then
			local timer crashTimer = LoadTimerHandle(hash, StringHash(functionName), 0)
			call SaveBoolean(hash, GetHandleId(crashTimer), 0, false)
			set crashTimer = null
		endif
	endfunction

	private function GetClockString takes real rawSeconds returns string
		local integer seconds
		local integer minutes = R2I(rawSeconds) / 60
		local string clock
	
		set seconds = ModuloInteger(R2I(rawSeconds), 60)
		set clock = I2S(minutes) + ":"
		if seconds >= 10 then
			set clock = clock + I2S(seconds)
		else
			set clock = clock + "0" + I2S(seconds)
		endif
		return clock
	endfunction

	private function TimeExpires takes nothing returns nothing
		local noArgCallback onExpire = HeroSelectionOnExpire
		call ExecuteFunc("HeroSelectionForceEnd")
		call PauseTimer(countdownUpdateTimer)
		call BlzFrameSetText(timerFrame, TIMER_TEXT + GetClockString(0))
		call onExpire.evaluate()
	endfunction

	private function GetLocZ takes real x, real y returns real
		local location tempLoc = Location(x,y)
		local real z = GetLocationZ(tempLoc)
		call RemoveLocation(tempLoc)
		set tempLoc = null
		return z
	endfunction

	private function TimerCounterPlus takes timer t returns integer
		local integer counter = LoadInteger( hash , GetHandleId(t) , 0 )
		set counter = counter + 1
		call SaveInteger( hash , GetHandleId(t) , 0 , counter )
		return counter
	endfunction
	
	private function PlaySoundLocal takes string soundPath, boolean localPlayerCanHearSound returns nothing
		local real volume
		local sound s = CreateSound( soundPath , FALSE, FALSE, FALSE, 10, 10, "DefaultEAXON")
		call SetSoundChannel(s, 0)
		if localPlayerCanHearSound then
			set volume = 100
		else
			set volume = 0
		endif
		call SetSoundVolumeBJ( s , volume )
		call StartSound(s)
		call KillSoundWhenDone(s)
		set s = null
	endfunction

	private function GetPickedHeroDisplayedName takes integer heroIndex, boolean capitalize returns string
		local unit tempUnit
		local string name
		static if USE_HERO_PROPER_NAME then
			set tempUnit = CreateUnit(Player(PLAYER_NEUTRAL_PASSIVE), Hero.list[heroIndex].unitId, GARBAGE_DUMP_X, GARBAGE_DUMP_Y, 0)
			set name = "|cffffcc00" + GetHeroProperName(tempUnit) + "|r"
			call RemoveUnit(tempUnit)
			set tempUnit = null
			return name
		else
			if capitalize then
				return "The |cffffcc00" + Hero.list[heroIndex].name + "|r"
			else
				return "the |cffffcc00" + Hero.list[heroIndex].name + "|r"
			endif
		endif
	endfunction

	//==========================================================================================================================================================
	//Hero Menu
	//==========================================================================================================================================================

	private function HeroCanBePicked takes integer heroIndex returns boolean
		return heroIndex != 0 and not ((heroIndexWasPicked[heroIndex] and not HERO_CAN_BE_PICKED_MULTIPLE_TIMES) or Hero.list[heroIndex].unavailable or heroIndexWasBanned[heroIndex])
	endfunction

	private function GetHeroAbilityName takes integer id returns string
		local string tooltip = BlzGetAbilityTooltip(id, 0)
		local integer length = StringLength(tooltip)
		local integer delimLength = StringLength(HERO_ABILITY_LEVEL_DELIMITER)
		local integer i = 1
		local integer j

		if HERO_ABILITY_LEVEL_DELIMITER == null or HERO_ABILITY_LEVEL_DELIMITER == "" then
			return tooltip
		endif

		loop
			exitwhen i > length - delimLength
			set j = 0
			loop
				exitwhen SubString(tooltip, i+j, i+j+1) != SubString(HERO_ABILITY_LEVEL_DELIMITER, j, j+1)
				if j == delimLength - 1 then
					return SubString(tooltip, 0, i)
				endif
				set j = j + 1
			endloop
			set i = i + 1
		endloop
		return tooltip
	endfunction

	private function GetRandomHero takes integer currentSelection, integer P returns integer
		local integer array heroesAvailable
		local integer numChars = 0
		local integer i
		local boolean currentSelectionAvailable = false
		local integer currentSelectionChar
		
		set i = 1
		loop
		exitwhen i > NUMBER_OF_HEROES
			if HeroCanBePicked(i) and not Hero.list[i].unavailableToTeam[TEAM_OF_PLAYER[P]] then
				set numChars = numChars + 1
				set heroesAvailable[numChars] = i
				if currentSelection == i then
					set currentSelectionAvailable = true
					set currentSelectionChar = numChars
				endif
			endif
			set i = i + 1
		endloop
		if currentSelectionAvailable and currentSelection != 0 then
			//Return random hero not currently selected.
			return heroesAvailable[ModuloInteger( currentSelectionChar + GetRandomInt(0,numChars-2) , numChars ) + 1]
		else
			return heroesAvailable[GetRandomInt(1,numChars)]
		endif
	endfunction

	private function SetButtonFrames takes integer whichButton returns nothing
		set heroSelectionButtonIcon[whichButton] = BlzFrameGetChild(heroSelectionButton[whichButton],0)
		set heroSelectionButtonIconClicked[whichButton] = BlzFrameGetChild(heroSelectionButton[whichButton],1)
		call BlzFrameClearAllPoints(heroSelectionButtonIconClicked[whichButton])
		call BlzFrameSetPoint(heroSelectionButtonIconClicked[whichButton], FRAMEPOINT_BOTTOMLEFT, heroSelectionButton[whichButton], FRAMEPOINT_BOTTOMLEFT, 0.001, 0.001)
		call BlzFrameSetPoint(heroSelectionButtonIconClicked[whichButton], FRAMEPOINT_TOPRIGHT, heroSelectionButton[whichButton], FRAMEPOINT_TOPRIGHT, -0.001, -0.001)
	endfunction

	private function SetButtonTooltip takes integer whichButton, string whichTitle, string whichTooltip returns nothing
		set heroSelectionButtonTooltip[whichButton] = BlzCreateFrame("CustomTooltip", heroSelectionButton[whichButton], 0, 0)
		if TOOLTIP_LOCK_TOP then
			call BlzFrameSetAbsPoint( heroSelectionButtonTooltip[whichButton] , FRAMEPOINT_TOPLEFT , TOOLTIP_LEFT_X , TOOLTIP_Y )
		else
			call BlzFrameSetAbsPoint( heroSelectionButtonTooltip[whichButton] , FRAMEPOINT_BOTTOMLEFT , TOOLTIP_LEFT_X , TOOLTIP_Y )
		endif
		call BlzFrameSetTooltip( heroSelectionButton[whichButton] , heroSelectionButtonTooltip[whichButton] )
		
		set heroSelectionButtonTooltipTitle[whichButton] = BlzFrameGetChild(heroSelectionButtonTooltip[whichButton],0)
		set heroSelectionButtonTooltipText[whichButton] = BlzFrameGetChild(heroSelectionButtonTooltip[whichButton],1)
		
		call BlzFrameSetText( heroSelectionButtonTooltipTitle[whichButton] , whichTitle )
		call BlzFrameSetText( heroSelectionButtonTooltipText[whichButton] , whichTooltip )
		call BlzFrameSetSize(heroSelectionButtonTooltipText[whichButton] , TOOLTIP_WIDTH - 0.01 , 0.0 )
		call BlzFrameSetSize(heroSelectionButtonTooltip[whichButton] , TOOLTIP_WIDTH , BlzFrameGetHeight(heroSelectionButtonTooltipText[whichButton]) + TOOLTIP_BASE_HEIGHT)
	endfunction
	
	private function SetButtonTextures takes integer whichButton, boolean disabledTexture returns nothing
		local integer i
		local integer stringLength
		local string disabledPath
		local string path

		if whichButton <= NUMBER_OF_HEROES then
			set path = Hero.list[whichButton].iconPath
		elseif whichButton == RANDOM_HERO then
			set path = RANDOM_HERO_ICON
		elseif whichButton == SUGGEST_RANDOM then
			set path = SUGGEST_RANDOM_ICON
		elseif whichButton == PAGE_DOWN then
			set path = MENU_PAGE_DOWN_ICON
		else
			set path = MENU_PAGE_UP_ICON
		endif

		if not disabledTexture then
			call BlzFrameSetTexture(heroSelectionButtonIcon[whichButton] , path , 0 , true)
			call BlzFrameSetTexture(heroSelectionButtonIconClicked[whichButton] , path , 0 , true)
		else
			set stringLength = StringLength(path)
			set i = 0
			loop
				exitwhen i > stringLength - 3
				if SubString(path, i, i+1) == "B" and SubString(path, i+1, i+2) == "T" and SubString(path, i+2, i+3) == "N" then
					set disabledPath = "ReplaceableTextures\\CommandButtonsDisabled\\DISBTN" + SubString(path, i+3, stringLength)
					call BlzFrameSetTexture(heroSelectionButtonIcon[whichButton] , disabledPath, 0 , true)
					call BlzFrameSetTexture(heroSelectionButtonIconClicked[whichButton] , disabledPath, 0 , true)
					return
				endif
				 set i = i + 1
			endloop
		endif
	endfunction

	//==========================================================================================================================================================
	//Caption
	//==========================================================================================================================================================
	
	private function TextColor takes nothing returns nothing
		local integer counter = TimerCounterPlus(GetExpiredTimer())
		local real colorState = (1 + Cos(2*bj_PI*counter/(CAPTION_CYCLE_TIME/0.02)))/2
		local integer r = R2I((1 - colorState)*r1 + colorState*r2)
		local integer g = R2I((1 - colorState)*g1 + colorState*g2)
		local integer b = R2I((1 - colorState)*b1 + colorState*b2)
		local integer array c
		local integer i
		local string colorString = "|cff"

		set c[1] = r/16
		set c[2] = r - 16*c[1]
		set c[3] = g/16
		set c[4] = g - 16*c[3]
		set c[5] = b/16
		set c[6] = b - 16*c[5]

		set i = 1
		loop
			exitwhen i > 6
			set colorString = colorString + SubString(HEX_STRING, c[i], c[i] + 1)
			set i = i + 1
		endloop

		if CAPTION_FADEOUT_TIME != -1 then
			if playerHasHero[localPlayerId] then
				if CAPTION_FADEOUT_TIME > 0 then
					set captionAlphaMultiplier = RMaxBJ(0, captionAlphaMultiplier - 1/(50*CAPTION_FADEOUT_TIME))
				else
					set captionAlphaMultiplier = 0
				endif
			endif
		endif

		call BlzFrameSetText(captionFrame, colorString + HERO_SELECTION_CAPTION + "|r")
		call BlzFrameSetAlpha(captionFrame, R2I(captionAlphaMultiplier*((1 - colorState)*CAPTION_ALPHA_1 + colorState*CAPTION_ALPHA_2)))
	endfunction

	private function HexToInt takes string hexString returns integer
		local integer i = 0
		local integer int = 0
		local string firstChar = StringCase(SubString(hexString, 0, 1), false)
		local string secondChar = StringCase(SubString(hexString, 1, 2), false)
		local boolean firstCharFound = false
		local boolean secondCharFound = false

		set i = 0
		loop
			exitwhen i > 15 or (firstCharFound and secondCharFound)
			if not firstCharFound and firstChar == SubString(HEX_STRING, i, i + 1) then
				set int = int + 16*i
				set firstCharFound = true
			endif
			if not secondCharFound and secondChar == SubString(HEX_STRING, i, i + 1) then
				set int = int + i
				set secondCharFound = true
			endif
			set i = i + 1
		endloop

		return int
	endfunction
	
	private function AnimateCaption takes nothing returns nothing
		local integer i
		local string s
		local integer j

		//Get RGB values of CAPTION_COLOR_1 and CAPTION_COLO_2.
		set r1 = HexToInt(SubString(CAPTION_COLOR_1, 4, 6))
		set g1 = HexToInt(SubString(CAPTION_COLOR_1, 6, 8))
		set b1 = HexToInt(SubString(CAPTION_COLOR_1, 8, 10))
		set r2 = HexToInt(SubString(CAPTION_COLOR_2, 4, 6))
		set g2 = HexToInt(SubString(CAPTION_COLOR_2, 6, 8))
		set b2 = HexToInt(SubString(CAPTION_COLOR_2, 8, 10))

		call TimerStart( captionTimer , 0.02 , true , function TextColor )
		call SaveInteger( hash , GetHandleId(captionTimer) , 0 , 0 )
	endfunction
	
	//==========================================================================================================================================================
	//Hero Effects
	//==========================================================================================================================================================

	private function FadeInBackgroundHero takes nothing returns nothing
		local timer t = GetExpiredTimer()
		local integer counter = TimerCounterPlus(t)
		local integer P = LoadInteger( hash , GetHandleId(t) , 1 )
		local boolean concealed = LoadBoolean( hash , GetHandleId(t) , 2 )
		local Hero whichHero = Hero.list[pickedHeroIndex[P]]

		//Hero for owner fades in later than for other players since it's still in the front.
		local integer ownerDelay = R2I(50*(FOREGROUND_HERO_FADEOUT_DELAY + FOREGROUND_HERO_FADEOUT_TIME)/BACKGROUND_HERO_FADEIN_TIME)
		local string modelPath
		
		if localPlayerId != P and counter <= 51 then
			if concealed then
				call BlzSetSpecialEffectAlpha( backgroundHero[P] , R2I(2.5*counter) )
				if whichHero.needsHeroGlow then
					call BlzSetSpecialEffectAlpha( backgroundHeroGlow[P], R2I(2.5*counter) )
				endif
			else
				call BlzSetSpecialEffectAlpha( backgroundHero[P] , 5*counter )
				if whichHero.needsHeroGlow then
					call BlzSetSpecialEffectAlpha( backgroundHeroGlow[P], 5*counter )
				endif
			endif
		elseif localPlayerId == P and counter > ownerDelay then
			call BlzSetSpecialEffectAlpha( backgroundHero[P] , 5*(counter - ownerDelay))
			if whichHero.needsHeroGlow then
				call BlzSetSpecialEffectAlpha( backgroundHeroGlow[P], 5*(counter - ownerDelay))
			endif
		endif

		static if CREATE_SHADOWS then
			if counter == ownerDelay then
				call RemoveDestructable(backgroundHeroShadow[P])
				set backgroundHeroShadow[P] = CreateDestructable(SHADOW_DESTRUCTABLE_ID, BACKGROUND_HERO_X[P], BACKGROUND_HERO_Y[P], 0, 1, 0)
			endif
		endif
		
		if counter == 51 + ownerDelay then
			if BACKGROUND_HERO_SELF_HIGHLIGHT != null then
				if localPlayerId == P then
					set modelPath = BACKGROUND_HERO_SELF_HIGHLIGHT
				else
					set modelPath = ""
				endif
				set backgroundHeroHighlight[P] = AddSpecialEffect(modelPath, BACKGROUND_HERO_X[P], BACKGROUND_HERO_Y[P])
				call BlzSetSpecialEffectZ(backgroundHeroHighlight[P], GetLocZ(BACKGROUND_HERO_X[P], BACKGROUND_HERO_Y[P]) + BACKGROUND_HERO_HIGHLIGHT_Z)
			endif
			call FlushChildHashtable( hash , GetHandleId(t) )
			call DestroyTimer(t)
		endif
		set t = null
	endfunction
	
	private function FadeoutForegroundHero takes nothing returns nothing
		local timer t = GetExpiredTimer()
		local integer counter = TimerCounterPlus(t)
		local integer P = LoadInteger( hash , GetHandleId(t) , 1 )
	
		if counter < 51 and FOREGROUND_HERO_FADEOUT_TIME > 0 then
			call BlzSetSpecialEffectAlpha( selectedHero[P] , 255 - 5*counter )
			call BlzSetSpecialEffectAlpha( selectedHeroGlow[P] , 255 - 5*counter )
			call TimerStart( t , FOREGROUND_HERO_FADEOUT_TIME/50.0 , false , function FadeoutForegroundHero )
		else
			call BlzSetSpecialEffectPosition( selectedHero[P] , GARBAGE_DUMP_X , GARBAGE_DUMP_Y , 0 )
			call DestroyEffect( selectedHero[P] )
			call DestroyEffect( selectedHeroGlow[P] )
			call FlushChildHashtable( hash , GetHandleId(t) )
			call DestroyTimer(t)
		endif
		set t = null
	endfunction

	private function ResetAnimation takes nothing returns nothing
		local timer t = GetExpiredTimer()
		local integer id = GetHandleId(t)
		local integer P = LoadInteger( hash , id , 0 )
		local integer heroIndex = LoadInteger( hash , id , 1 )
		local boolean animateBackgroundHero = LoadBoolean( hash , id , 2)
	
		if animateBackgroundHero then
			if localPlayerId != P then
				call BlzSpecialEffectClearSubAnimations(backgroundHero[P])
				call BlzPlaySpecialEffect( backgroundHero[P] , ANIM_TYPE_STAND )
			endif
		else
			if preselectedHeroIndex[P] == heroIndex then
				call BlzSpecialEffectClearSubAnimations(selectedHero[P])
				call BlzPlaySpecialEffect( selectedHero[P] , ANIM_TYPE_STAND )
			endif
		endif
	
		call FlushChildHashtable( hash , GetHandleId(t) )
		call DestroyTimer(t)
		set t = null
	endfunction

	private function PlayHeroAnimation takes integer P, integer heroIndex, boolean animateBackgroundHero returns nothing
		local timer t = CreateTimer()
		local integer id = GetHandleId(t)
		if animateBackgroundHero then
			if localPlayerId != P and (not CONCEAL_HERO_PICKS_FROM_ENEMIES or (TEAM_OF_PLAYER[localPlayerId] == TEAM_OF_PLAYER[P] and TEAM_OF_PLAYER[P] != 0)) then
				call BlzSpecialEffectAddSubAnimation( backgroundHero[P] , Hero.list[heroIndex].selectSubAnim )
				call BlzPlaySpecialEffect( backgroundHero[P] , Hero.list[heroIndex].selectAnim )
			endif
		else
			call BlzSpecialEffectAddSubAnimation( selectedHero[P] , Hero.list[heroIndex].selectSubAnim )
			call BlzPlaySpecialEffect( selectedHero[P] , Hero.list[heroIndex].selectAnim )
		endif

		call TimerStart( t , Hero.list[heroIndex].selectAnimLength , false , function ResetAnimation )
		call SaveInteger( hash , id , 0 , P )
		call SaveInteger( hash , id , 1 , heroIndex )
		call SaveBoolean( hash , id , 2 , animateBackgroundHero)
		set t = null
	endfunction

	private function CreateNewForegroundHero takes integer P, integer heroIndex returns nothing
		local string modelPath = ""
		local string glowPath = ""
		local real locZ
		local Hero whichHero = Hero.list[heroIndex]

		if localPlayerId == P then
			if heroIndex == RANDOM_HERO then
				set modelPath = "Objects\\InventoryItems\\QuestionMark\\QuestionMark.mdl"
			else
				set modelPath = whichHero.modelPath
			endif
			if whichHero.needsHeroGlow then
				set glowPath = "GeneralHeroGlow.mdx"
			endif
		endif

		call BlzSetSpecialEffectPosition( selectedHero[P] , GARBAGE_DUMP_X , GARBAGE_DUMP_Y , 0 )
		call BlzSetSpecialEffectTimeScale( selectedHero[P] , 9999)
		call BlzSetSpecialEffectScale(selectedHero[P], 0)
		call DestroyEffect(selectedHero[P])
		call DestroyEffect(selectedHeroGlow[P])
		set selectedHero[P] = AddSpecialEffect( modelPath , localForegroundHeroX , localForegroundHeroY )
		set selectedHeroGlow[P] = AddSpecialEffect( glowPath , localForegroundHeroX , localForegroundHeroY )
		set locZ = GetLocZ(localForegroundHeroX, localForegroundHeroY)
		call BlzSetSpecialEffectZ( selectedHero[P], locZ + FOREGROUND_HERO_Z )
		call BlzSetSpecialEffectZ( selectedHeroGlow[P], locZ + FOREGROUND_HERO_Z )
		call BlzSetSpecialEffectYaw( selectedHero[P] , Deg2Rad(localHeroSelectionAngle) + bj_PI )
		if heroIndex != RANDOM_HERO then
			call BlzSetSpecialEffectScale( selectedHero[P] , whichHero.scalingValue )
		endif
		if PHANTOM_HERO_WHEN_CANNOT_BE_PICKED and (heroSelectionDisabledForPlayer[P] or not HeroCanBePicked(heroIndex)) then
			call BlzSetSpecialEffectColor( selectedHero[P], 0, 0, 0 )
			call BlzSetSpecialEffectAlpha( selectedHero[P], 128 )
		else
			call BlzSetSpecialEffectColor( selectedHero[P] , whichHero.red , whichHero.green , whichHero.blue)
		endif
	endfunction

    private function DeleteBackgroundHero takes integer P returns nothing
        call BlzSetSpecialEffectPosition(backgroundHero[P], GARBAGE_DUMP_X, GARBAGE_DUMP_Y, 0)
        call DestroyEffect(backgroundHero[P])
        call DestroyEffect(backgroundHeroGlow[P])
        call DestroyEffect(backgroundHeroHighlight[P])
        static if CREATE_SHADOWS then
            call RemoveDestructable(backgroundHeroShadow[P])
        endif
        static if PLAYER_TEXT_TAGS then
            call DestroyTextTag(backgroundHeroTextTag[P])
        endif
    endfunction

	//==========================================================================================================================================================
	//Miscellaneous.
	//==========================================================================================================================================================

	private function UpdateTimerFrame takes nothing returns nothing
		if inBanPhase then
			call BlzFrameSetText(timerFrame, TIMER_BAN_PHASE_TEXT + GetClockString(TimerGetRemaining(countdownTimer)))
		else
			call BlzFrameSetText(timerFrame, TIMER_TEXT + GetClockString(TimerGetRemaining(countdownTimer)))
		endif
	endfunction
	
	private function LockSelecterCamera takes nothing returns nothing
		local integer i = 1
	
		loop
		exitwhen i > numSelectingPlayers
			if isInHeroSelection[playerNumberOfSlot[i]] then
				call CameraSetupApplyForPlayer( true, heroSelectionCamera, Player(playerNumberOfSlot[i]), 0 )
			endif
			set i = i + 1
		endloop
	endfunction

	//==========================================================================================================================================================
	//End hero selection.
	//==========================================================================================================================================================

	private function EscapePlayer takes player whichPlayer returns nothing
		local integer P = GetPlayerId(whichPlayer)
		local playerCallback onEscape = HeroSelectionOnEscape
        local framehandle consoleUIBackdrop = BlzGetFrameByName("ConsoleUIBackdrop",0)

		if not isInHeroSelection[P] then
			static if HERO_SELECTION_ENABLE_DEBUG_MODE then
				call BJDebugMsg("|cffff0000Warning:|r Attempted to escape player who is not in hero selection...")
			endif
			return
		endif

		call InitCrashCheck("EscapePlayer")

		set isInHeroSelection[P] = false
		set preselectedHeroIndex[P] = 0
		set isRepicking[P] = false
		set playerHasBan[P] = false
		set numPlayersInSelection = numPlayersInSelection - 1
		call DestroyEffect(selectedHero[P])
		call ResetToGameCameraForPlayer(whichPlayer, 0)

		if localPlayerId == P then
			call BlzFrameSetVisible(heroSelectionMenu, false)
			call BlzFrameSetVisible(captionFrame, false)
			call BlzFrameSetVisible(timerFrame, false)
			static if HIDE_GAME_UI then
				call BlzHideOriginFrames(false)
				call BlzFrameSetVisible(consoleUIBackdrop, true)
			endif
		endif

		call onEscape.evaluate(whichPlayer)

		call NoCrash("EscapePlayer")
	endfunction
	
	private function EndHeroSelection takes nothing returns nothing
		local integer i = 1
		local integer P
		local noArgCallback onFinal = HeroSelectionOnFinal

		call InitCrashCheck("EndHeroSelection")
	
		call PauseTimer(lockCameraTimer)
		call PauseTimer(countdownTimer)
		call PauseTimer(countdownUpdateTimer)

		static if not ESCAPE_PLAYER_AFTER_SELECTING then
			loop
				exitwhen i > numSelectingPlayers
				set P = playerNumberOfSlot[i]
				call EscapePlayer(Player(P))
				set i = i + 1
			endloop
		endif

		set i = 1
		loop
			exitwhen i > numSelectingPlayers
			set P = playerNumberOfSlot[i]
			call BlzSetSpecialEffectPosition(selectedHero[P], GARBAGE_DUMP_X, GARBAGE_DUMP_Y, 0)
			call DestroyEffect(selectedHero[P])
			call DestroyEffect(selectedHeroGlow[P])
			static if DELETE_BACKGROUND_HEROES_AFTER_END then
				call BlzSetSpecialEffectPosition(backgroundHero[P], GARBAGE_DUMP_X, GARBAGE_DUMP_Y, 0)
				call DeleteBackgroundHero(P)
			endif
			set i = i + 1
		endloop

		static if CREATE_SHADOWS then
			call RemoveDestructable(foregroundHeroShadow)
		endif

		call onFinal.evaluate()

		call NoCrash("EndHeroSelection")
	endfunction

	private function EscapePlayerCaller takes nothing returns nothing
		local timer t = GetExpiredTimer()
		call EscapePlayer(Player(LoadInteger(hash, GetHandleId(t), 0)))
		call FlushChildHashtable(hash, GetHandleId(t))
		call DestroyTimer(t)
		set t = null

		static if ESCAPE_PLAYER_AFTER_SELECTING then
			if numPlayersInSelection == 0 then
				call EndHeroSelection()
			endif
		endif
	endfunction

	private function EndHeroSelectionCaller takes nothing returns nothing
		local timer t = GetExpiredTimer()
		call EndHeroSelection()
		call DestroyTimer(t)
		set t = null
	endfunction

	private function OnPlayerLeave takes nothing returns nothing
		local player whichPlayer = GetTriggerPlayer()
		local integer P = GetPlayerId(whichPlayer)
		local integer i = 1
		local integer j = 1
		local playerCallback onLeave = HeroSelectionOnLeave

		call InitCrashCheck("OnPlayerLeave")

		loop
			exitwhen i > numSelectingPlayers
			if P == playerNumberOfSlot[i] then
				set j = i
				loop
					exitwhen j > numSelectingPlayers - 1
					set playerNumberOfSlot[j] = playerNumberOfSlot[j+1]
					set j = j + 1
				endloop
				exitwhen true
			endif
			set i = i + 1
		endloop
		set numSelectingPlayers = numSelectingPlayers - 1
		if isInHeroSelection[P] then
			call onLeave.evaluate(whichPlayer)
			set numPlayersInSelection = numPlayersInSelection - 1
			set isInHeroSelection[P] = false
		endif
		if playerHasHero[P] then
			set numPlayersWithHero = numPlayersWithHero - 1
			set playerHasHero[P] = false
			call BlzSetSpecialEffectPosition(selectedHero[P], GARBAGE_DUMP_X, GARBAGE_DUMP_Y, 0)
			call DestroyEffect(selectedHero[P])
			call DestroyEffect(selectedHeroGlow[P])
			call DestroyEffect(selectedHeroGlow[P])
			call BlzSetSpecialEffectPosition(backgroundHero[P], GARBAGE_DUMP_X, GARBAGE_DUMP_Y, 0)
			call DeleteBackgroundHero(P)
		elseif numPlayersWithHero == numSelectingPlayers and not isRepicking[P] then
			call TimerStart( CreateTimer() , LAST_PLAYER_SELECT_END_DELAY , false , function EndHeroSelectionCaller )
		endif
	
		call NoCrash("OnPlayerLeave")
	endfunction

	//==========================================================================================================================================================
	//Cycle Page.
	//==========================================================================================================================================================

	private function CyclePage takes nothing returns nothing
		local player whichPlayer = GetTriggerPlayer()
		local integer P = GetPlayerId(whichPlayer)
		local integer i

		call PlaySoundLocal( "Sound\\Interface\\BigButtonClick.flac" , localPlayerId == P )

		if localPlayerId == P then
			if BlzGetTriggerFrame() == heroSelectionButton[PAGE_DOWN] then
				set currentPage = ModuloInteger(currentPage - 2, NUMBER_OF_PAGES) + 1
			else
				set currentPage = ModuloInteger(currentPage, NUMBER_OF_PAGES) + 1
			endif
			call BlzFrameSetVisible(heroSelectionButtonHighlight, PAGE_OF_CATEGORY[Hero.list[preselectedHeroIndex[P]].category] == currentPage)

			set i = 1
			loop
				exitwhen i > NUMBER_OF_CATEGORIES
				call BlzFrameSetVisible(heroSelectionCategory[i], PAGE_OF_CATEGORY[i] == currentPage)
				set i = i + 1
			endloop

			set i = 1
			loop
				exitwhen i > NUMBER_OF_HEROES
				call BlzFrameSetVisible(heroSelectionButton[i], PAGE_OF_CATEGORY[Hero.list[i].category] == currentPage)
				set i = i + 1
			endloop
		endif
	endfunction
	
	//==========================================================================================================================================================
	//Ban Hero.
	//==========================================================================================================================================================

	private function ExecuteBan takes Hero whichHero, boolean disable returns nothing
		local integer i
		local integer P
		if disable then
			call BlzFrameSetText(heroSelectionButtonTooltipText[whichHero.index] , BlzGetAbilityExtendedTooltip(whichHero.tooltipAbility, 0) + "|n|n|cffff0000This hero was banned.|r")
		else
			call BlzFrameSetText(heroSelectionButtonTooltipText[whichHero.index] , BlzGetAbilityExtendedTooltip(whichHero.tooltipAbility, 0))
		endif
		call BlzFrameSetSize(heroSelectionButtonTooltipText[whichHero.index] , TOOLTIP_WIDTH - 0.01 , 0.0 )
		call BlzFrameSetSize(heroSelectionButtonTooltip[whichHero.index] , TOOLTIP_WIDTH , BlzFrameGetHeight(heroSelectionButtonTooltipText[whichHero.index]) + TOOLTIP_BASE_HEIGHT)

		set heroIndexWasBanned[whichHero.index] = true
		call SetButtonTextures(whichHero.index, not HeroCanBePicked(whichHero.index))

		set i = 1
		loop
			exitwhen i > numSelectingPlayers
			set P = playerNumberOfSlot[i]
			if localPlayerId == P and preselectedHeroIndex[P] == whichHero then
				call BlzFrameSetEnable( heroAcceptButton , false )
				call BlzFrameSetEnable( heroBanButton , false )
				static if PHANTOM_HERO_WHEN_CANNOT_BE_PICKED then
					call BlzSetSpecialEffectAlpha(selectedHero[P], 128)
					call BlzSetSpecialEffectColor(selectedHero[P], 0, 0, 0)
				endif
			endif
			set i = i + 1
		endloop
	endfunction

	private function BanHero takes nothing returns nothing
		local string message
		local player whichPlayer = GetTriggerPlayer()
		local integer P = GetPlayerId(whichPlayer)
		local integer heroIndex = preselectedHeroIndex[P]

		call InitCrashCheck("BanHero")

		if localPlayerId == P then
			call BlzFrameSetEnable(heroBanButton, false)
		endif
		set playerHasBan[P] = false
		
		set message = GetPickedHeroDisplayedName(heroIndex, true) + " was banned."

		static if LIBRARY_NeatMessages then
			call NeatMessage(message)
		else
			call DisplayTextToPlayer(GetLocalPlayer(), TEXT_MESSAGE_X_OFFSET, TEXT_MESSAGE_Y_OFFSET, message)
		endif
		if OTHER_PLAYER_HERO_PICK_SOUND != null then
			call PlaySoundLocal(OTHER_PLAYER_HERO_PICK_SOUND, true)
		endif

		call ExecuteBan(Hero.list[heroIndex], true)

		call NoCrash("BanHero")
	endfunction

	//==========================================================================================================================================================
	//Pick Hero.
	//==========================================================================================================================================================
	
	private function PickHero takes nothing returns nothing
		local integer i
		local integer P
		local integer Q
		local string message
		local player whichPlayer
		local integer heroIndex
		local timer t
		local string modelPath
		local boolean wasRandomSelect
		local boolean concealed
		local integer id
		local boolean allHumanPlayersHaveHeroes
		local effect pickEffect
		local noArgCallback onLast = HeroSelectionOnLast
		local onPickCallback onPick = HeroSelectionOnPick

		call InitCrashCheck("PickHero")
		
		if not isForcedSelect then
			set whichPlayer = GetTriggerPlayer()
			set P = GetPlayerId(whichPlayer)
			set heroIndex = preselectedHeroIndex[P]
		else
			set P = storePlayerIndex
			set whichPlayer = Player(P)
			set heroIndex = storeHeroIndex
		endif

		set concealed = CONCEAL_HERO_PICKS_FROM_ENEMIES and localPlayerId != P and (TEAM_OF_PLAYER[localPlayerId] != TEAM_OF_PLAYER[P] or TEAM_OF_PLAYER[P] == 0)
		
		//Random
		
		if heroIndex == RANDOM_HERO then
			set heroIndex = GetRandomHero(0, P)
			
			if PICK_SOUND != null then
				call PlaySoundLocal(PICK_SOUND , localPlayerId == P )
			endif

			set wasRandomSelect = true
			
			static if CREATE_FOREGROUND_HERO then
				if localPlayerId == P then
					set modelPath = Hero.list[heroIndex].modelPath
				else
					set modelPath = ""
				endif
				call DestroyEffect(selectedHero[P])
				call BlzSetSpecialEffectPosition( selectedHero[P] , GARBAGE_DUMP_X , GARBAGE_DUMP_Y , 0 )
				set selectedHero[P] = AddSpecialEffect( modelPath , localForegroundHeroX , localForegroundHeroY )
				call BlzSetSpecialEffectYaw( selectedHero[P] , Deg2Rad(localHeroSelectionAngle) + bj_PI )
				call BlzSetSpecialEffectScale( selectedHero[P] , Hero.list[heroIndex].scalingValue )
			endif
		else
			call PlaySoundLocal( "Sound\\Interface\\BigButtonClick.flac" , localPlayerId == P )
			if PICK_SOUND != null then
				call PlaySoundLocal(PICK_SOUND , localPlayerId == P )
			endif
			set wasRandomSelect = false
		endif
		
		//Disable Buttons for selecting player.
		
		set i = 1
		loop
		exitwhen i > SUGGEST_RANDOM
			if localPlayerId == P then
				call SetButtonTextures(i, true)
			endif
			set i = i + 1
		endloop
		
		if localPlayerId == P then
			call BlzFrameSetEnable( heroAcceptButton , false )
			call BlzFrameSetEnable( heroBanButton , false )
		endif

		//Disable button for other players that have pre-selected that hero.
	
		static if CREATE_FOREGROUND_HERO then
			static if not HERO_CAN_BE_PICKED_MULTIPLE_TIMES then
				call SetButtonTextures(heroIndex, true)
				set i = 1
				loop
				exitwhen i > numSelectingPlayers
					if localPlayerId == playerNumberOfSlot[i] and preselectedHeroIndex[playerNumberOfSlot[i]] == heroIndex then
						call BlzFrameSetEnable( heroAcceptButton , false )
						call BlzFrameSetEnable( heroBanButton , false )
						static if PHANTOM_HERO_WHEN_CANNOT_BE_PICKED then
							if playerNumberOfSlot[i] != P then
								call BlzSetSpecialEffectAlpha(selectedHero[playerNumberOfSlot[i]], 128)
								call BlzSetSpecialEffectColor(selectedHero[playerNumberOfSlot[i]], 0, 0, 0)
							endif
						endif
					endif
					set i = i + 1
				endloop
			endif
		endif
		
		//Set variables
	
		set heroIndexWasPicked[heroIndex] = true
		set pickedHeroIndex[P] = heroIndex
		set heroIdOfPlayer[P] = Hero.list[heroIndex].unitId
		set heroIconPathOfPlayer[P] = "ReplaceableTextures\\CommandButtons\\" + Hero.list[heroIndex].iconPath
		set playerHasHero[P] = true
		set playerHasBan[P] = false
		set numPlayersWithHero = numPlayersWithHero + 1
		
		//Text messages

		if OTHER_PLAYER_HERO_PICK_SOUND != null then
			call PlaySoundLocal(OTHER_PLAYER_HERO_PICK_SOUND, localPlayerId != P and (MESSAGE_EVEN_WHEN_CONCEALED or not concealed))
		endif
	
		set i = 1
		loop
		exitwhen i > numSelectingPlayers
			set Q = playerNumberOfSlot[i]
			if P != Q then
				if CREATE_TEXT_MESSAGE_ON_PICK then
					set message = ""
					if (CONCEAL_HERO_PICKS_FROM_ENEMIES and (TEAM_OF_PLAYER[P] != TEAM_OF_PLAYER[Q] or TEAM_OF_PLAYER[P] == 0)) then
						if MESSAGE_EVEN_WHEN_CONCEALED then
							set message = message + coloredPlayerName[P] + " has selected a hero."
						endif
					else
						if wasRandomSelect then
							set message = message + coloredPlayerName[P] + " has randomly selected " + GetPickedHeroDisplayedName(heroIndex, false) + "."
						else
							set message = message + coloredPlayerName[P] + " has selected " + GetPickedHeroDisplayedName(heroIndex, false) + "."
						endif
					endif
					static if INCLUDE_PROGRESSION_IN_MESSAGE then
						set message = message + " " + I2S(numPlayersWithHero) + "/" + I2S(numSelectingPlayers) + " players have selected."
					endif
					if message != "" then
						static if LIBRARY_NeatMessages then
							call NeatMessageToPlayer(Player(Q), message)
						else
							call DisplayTextToPlayer(Player(Q), TEXT_MESSAGE_X_OFFSET, TEXT_MESSAGE_Y_OFFSET, message)
						endif
					endif
				endif
			else
				if wasRandomSelect then
					set message = "You randomly selected " + GetPickedHeroDisplayedName(heroIndex, false) + "."
				else
					set message = "You selected " + GetPickedHeroDisplayedName(heroIndex, false) + "."
				endif
				static if INCLUDE_PROGRESSION_IN_MESSAGE then
					set message = message + " " + I2S(numPlayersWithHero) + "/" + I2S(numSelectingPlayers) + " players have selected."
				endif
				static if LIBRARY_NeatMessages then
					call NeatMessageToPlayer(Player(P), message)
				else
					call DisplayTextToPlayer(Player(P), TEXT_MESSAGE_X_OFFSET, TEXT_MESSAGE_Y_OFFSET, message)
				endif
			endif
			set i = i + 1
		endloop
		
		//Foreground hero
		
		static if CREATE_FOREGROUND_HERO then
			if PICK_EFFECT != null then
				if localPlayerId == P then
					set modelPath = PICK_EFFECT
				else
					set modelPath = ""
				endif
			endif

			set pickEffect = AddSpecialEffect( modelPath , localForegroundHeroX , localForegroundHeroY )
			call BlzSetSpecialEffectZ(pickEffect, GetLocZ(localForegroundHeroX, localForegroundHeroY) + FOREGROUND_HERO_Z)
			call DestroyEffect(pickEffect)
			set pickEffect = null
		
			static if PLAY_ANIMATION_ON_PICK then
				call PlayHeroAnimation(P, heroIndex, false)
			endif

			static if PLAY_EMOTE_ON_PICK then
				call PlaySoundLocal( Hero.list[heroIndex].selectEmote , localPlayerId == P )
			endif

			if CREATE_BACKGROUND_HEROES or ESCAPE_PLAYER_AFTER_SELECTING or isRepicking[P] then
				set t = CreateTimer()
				call TimerStart( t , FOREGROUND_HERO_FADEOUT_DELAY , false , function FadeoutForegroundHero )
				call SaveInteger( hash , GetHandleId(t) , 0 , 0 )
				call SaveInteger( hash , GetHandleId(t) , 1 , P )
			endif
		endif
		
		//Create Background Hero
		
		static if CREATE_BACKGROUND_HEROES then
			if concealed then
				set modelPath = CONCEALED_HERO_EFFECT
			else
				set modelPath = Hero.list[heroIndex].modelPath
			endif
			set backgroundHero[P] = AddSpecialEffect( modelPath , BACKGROUND_HERO_X[P] , BACKGROUND_HERO_Y[P] )
			call BlzSetSpecialEffectScale( backgroundHero[P] , Hero.list[heroIndex].scalingValue )
			call BlzSetSpecialEffectAlpha( backgroundHero[P] , 0 )
			call BlzSetSpecialEffectYaw( backgroundHero[P] , Atan2(localForegroundHeroY - BACKGROUND_HERO_Y[P] + Sin(Deg2Rad(localHeroSelectionAngle))*BACKGROUND_HERO_FACING_POINT_OFFSET , localForegroundHeroX - BACKGROUND_HERO_X[P] + Cos(Deg2Rad(localHeroSelectionAngle))*BACKGROUND_HERO_FACING_POINT_OFFSET) )
			call BlzSetSpecialEffectColorByPlayer( backgroundHero[P] , whichPlayer )
			call BlzSetSpecialEffectColor( backgroundHero[P] , Hero.list[heroIndex].red , Hero.list[heroIndex].green , Hero.list[heroIndex].blue)

			if Hero.list[heroIndex].needsHeroGlow then
				set backgroundHeroGlow[P] = AddSpecialEffect( "GeneralHeroGlow.mdx" , BACKGROUND_HERO_X[P] , BACKGROUND_HERO_Y[P] )
				call BlzSetSpecialEffectAlpha( backgroundHeroGlow[P], 0 )
				call BlzSetSpecialEffectColorByPlayer( backgroundHeroGlow[P] , whichPlayer )
			endif

			if concealed then
				call BlzSetSpecialEffectColor(backgroundHero[P], 0, 0, 0)
			endif

			static if PLAY_ANIMATION_ON_BACKGROUND_HERO then
				call PlayHeroAnimation(P, heroIndex, true)
			endif
			
			if BACKGROUND_HERO_FADEIN_EFFECT != null then
				if localPlayerId != P then
					set modelPath = BACKGROUND_HERO_FADEIN_EFFECT
				else
					set modelPath = ""
				endif
				call DestroyEffect(AddSpecialEffect(modelPath, BACKGROUND_HERO_X[P], BACKGROUND_HERO_Y[P] ))
			endif

			static if PLAY_EMOTE_ON_BACKGROUND_HERO then
				call PlaySoundLocal(Hero.list[heroIndex].selectEmote, localPlayerId != P and isInHeroSelection[localPlayerId] and not concealed)
			endif

			static if CREATE_SHADOWS then
				if localPlayerId == P then
					set id = NO_SHADOW_DESTRUCTABLE_ID
				else
					set id = SHADOW_DESTRUCTABLE_ID
				endif
				set backgroundHeroShadow[P] = CreateDestructable(id, BACKGROUND_HERO_X[P], BACKGROUND_HERO_Y[P], 0, 1, 0)
			endif
			
			if BACKGROUND_HERO_FADEIN_TIME > 0 then
				set t = CreateTimer()
				call TimerStart( t , BACKGROUND_HERO_FADEIN_TIME/50.0 , true , function FadeInBackgroundHero )
				set id = GetHandleId(t)
				call SaveInteger( hash , id , 0 , 0 )
				call SaveInteger( hash , id , 1 , P )
				call SaveBoolean( hash , id , 2 , concealed)
			else
				if concealed then
					call BlzSetSpecialEffectAlpha( backgroundHero[P] , 128 )
				else
					call BlzSetSpecialEffectAlpha( backgroundHero[P] , 255 )
				endif
			endif
		endif

		if ESCAPE_PLAYER_AFTER_SELECTING or isRepicking[P] then
			set t = CreateTimer()
			call TimerStart( t , PLAYER_PICK_ESCAPE_DELAY , false , function EscapePlayerCaller)
			call SaveInteger(hash, GetHandleId(t), 0, P)
		endif
			
		//End hero selection.

		if not isRepicking[P] and numPlayersWithHero == numSelectingPlayers then
			call onLast.evaluate()
			if TIME_LIMIT > 0 then
				call PauseTimer(countdownTimer)
			endif
			static if not ESCAPE_PLAYER_AFTER_SELECTING then
				call TimerStart( CreateTimer() , LAST_PLAYER_SELECT_END_DELAY , false , function EndHeroSelectionCaller )
			endif
		endif

		static if COMPUTER_AUTO_PICK_RANDOM_HERO then
			if playerIsHuman[P] and not isRepicking[P] then
				set allHumanPlayersHaveHeroes = true
				set i = 1
				loop
					exitwhen i > numSelectingPlayers
					set P = playerNumberOfSlot[i]
					if playerIsHuman[P] and not playerHasHero[P] then
						set allHumanPlayersHaveHeroes = false
						exitwhen true
					endif
					set i = i + 1
				endloop

				if allHumanPlayersHaveHeroes then
					set i = 1
					loop
						exitwhen i > numSelectingPlayers
						set P = playerNumberOfSlot[i]
						if not playerIsHuman[P] and not playerHasHero[P] and isInHeroSelection[P] then
							set isForcedSelect = true
							set storePlayerIndex = P
							set storeHeroIndex = RANDOM_HERO
							call PickHero()
							set isForcedSelect = false
						endif
						set i = i + 1
					endloop
				endif
			endif
		endif

		call onPick.evaluate(whichPlayer, Hero.list[heroIndex], wasRandomSelect, isRepicking[P])

		call NoCrash("PickHero")
		set t = null
	endfunction

	//==========================================================================================================================================================
	//Preselect Hero.
	//==========================================================================================================================================================
	
	private function PreselectRandomCycle takes nothing returns nothing
		local timer t = GetExpiredTimer()
		local integer P = LoadInteger(hash , GetHandleId(t) , 0 )
		local integer heroIndexShown = LoadInteger( hash , GetHandleId(t) , 1 )
		local integer newHeroShown

		call InitCrashCheck("PreselectRandomCycle")
	
		if preselectedHeroIndex[P] == RANDOM_HERO and not playerHasHero[P] then
			set newHeroShown = GetRandomHero(heroIndexShown, P)
			call CreateNewForegroundHero(P, newHeroShown)
			call SaveInteger( hash , GetHandleId(t) , 1 , newHeroShown )
		else
			call FlushChildHashtable( hash , GetHandleId(t) )
			call DestroyTimer(t)
		endif
		call NoCrash("PreselectRandomCycle")
		set t = null
	endfunction
	
	private function PreselectHero takes nothing returns nothing
		local player whichPlayer = GetTriggerPlayer()
		local integer P = GetPlayerId(whichPlayer)
		local integer i
		local framehandle whichFrame = BlzGetTriggerFrame()
		local boolean isSuggest
		local boolean isRandom
		local integer heroIndex
		local string modelPath = ""
		local timer t
		local integer oldHero = preselectedHeroIndex[P]
		local integer id
		local effect preselectEffect
		local playerHeroHeroCallback onPreselect = HeroSelectionOnPreselect
		local integer firstRandomHero
		
		if heroPreselectionDisabledForPlayer[P] then
			return
		endif

		call InitCrashCheck("PreselectHero")
	
		call PlaySoundLocal( "Sound\\Interface\\BigButtonClick.flac" , localPlayerId == P )

		if PRESELECT_EFFECT != null then
			if localPlayerId == P then
				set modelPath = PRESELECT_EFFECT
			endif
			set preselectEffect = AddSpecialEffect( modelPath , localForegroundHeroX , localForegroundHeroY )
			call BlzSetSpecialEffectZ(preselectEffect, GetLocZ(localForegroundHeroX, localForegroundHeroY) + FOREGROUND_HERO_Z)
			call DestroyEffect(preselectEffect)
			set preselectEffect = null
		endif
		
		set i = 1
		loop
		exitwhen i > SUGGEST_RANDOM
			if whichFrame == heroSelectionButton[i] then
				set heroIndex = i
				if heroIndex == RANDOM_HERO then
					set isSuggest = false
					set isRandom = true
				elseif heroIndex == SUGGEST_RANDOM then
					set isSuggest = true
					set isRandom = false
				else
					set isSuggest = false
					set isRandom = false
				endif
				exitwhen true
			endif
			set i = i + 1
		endloop
		set whichFrame = null
		
		if isSuggest then
			set heroIndex = GetRandomHero(preselectedHeroIndex[P], P)
		endif
		
		if localPlayerId == P then
			if not isRandom then
				set i = 1
				loop
					exitwhen i > NUMBER_OF_ABILITY_FRAMES
					if Hero.list[heroIndex].abilities[i] != 0 then
						call BlzFrameSetTexture( heroSelectionAbility[i] , BlzGetAbilityIcon( Hero.list[heroIndex].abilities[i] ) , 0, true )
						call BlzFrameSetText( heroSelectionAbilityTooltipTitle[i] , GetHeroAbilityName(Hero.list[heroIndex].abilities[i]) )
						if Hero.list[heroIndex].isNonHeroAbility[i] then
							call BlzFrameSetText( heroSelectionAbilityTooltipText[i] , BlzGetAbilityExtendedTooltip( Hero.list[heroIndex].abilities[i] , 0 ) )
						else
							call BlzFrameSetText( heroSelectionAbilityTooltipText[i] , BlzGetAbilityResearchExtendedTooltip( Hero.list[heroIndex].abilities[i] , 0 ) )
						endif
						call BlzFrameSetSize(heroSelectionAbilityTooltipText[i] , TOOLTIP_WIDTH - 0.01 , 0.0 )
						call BlzFrameSetSize(heroSelectionAbilityTooltip[i] , TOOLTIP_WIDTH , BlzFrameGetHeight(heroSelectionAbilityTooltipText[i]) + TOOLTIP_BASE_HEIGHT)
						call BlzFrameSetVisible( heroSelectionAbility[i] , true )
					else
						call BlzFrameSetVisible( heroSelectionAbility[i] , false )
					endif
					set i = i + 1
				endloop
			else
				set i = 1
				loop
				exitwhen i > NUMBER_OF_ABILITY_FRAMES
					call BlzFrameSetVisible( heroSelectionAbility[i] , false )
					set i = i + 1
				endloop
			endif

			call BlzFrameSetEnable( heroAcceptButton , HeroCanBePicked(heroIndex) and not playerHasHero[P] and not heroSelectionDisabledForPlayer[P] )
			call BlzFrameSetEnable( heroBanButton , HeroCanBePicked(heroIndex) and heroIndex != RANDOM_HERO and playerHasBan[P] )
		endif

		static if CREATE_FOREGROUND_HERO then
			if not playerHasHero[P] and preselectedHeroIndex[P] != heroIndex then
				if not isRandom then
					call CreateNewForegroundHero(P, heroIndex)
					
					if HeroCanBePicked(heroIndex) and not heroSelectionDisabledForPlayer[P] then
						static if PLAY_EMOTE_ON_PRESELECT then
							call PlaySoundLocal( Hero.list[heroIndex].selectEmote , localPlayerId == P )
						endif
						static if PLAY_ANIMATION_ON_PRESELECT then
							call PlayHeroAnimation(P, heroIndex, false)
						endif
					endif
				elseif preselectedHeroIndex[P] != RANDOM_HERO then
					static if RANDOM_SELECT_CYCLE_STYLE then
						set firstRandomHero = GetRandomHero(0,P)
						call CreateNewForegroundHero(P, firstRandomHero)

						set t = CreateTimer()
						call TimerStart( t , RANDOM_SELECT_CYCLE_INTERVAL , true , function PreselectRandomCycle )
						call SaveInteger( hash , GetHandleId(t) , 0 , P )
						call SaveInteger( hash , GetHandleId(t) , 1 , firstRandomHero )
						set t = null
					else
						call CreateNewForegroundHero(P, RANDOM_HERO)
					endif
				endif
			endif

			static if CREATE_SHADOWS then
				call RemoveDestructable(foregroundHeroShadow)
				if preselectedHeroIndex[localPlayerId] == 0 or playerHasHero[localPlayerId] then
					set id = NO_SHADOW_DESTRUCTABLE_ID
				else
					set id = SHADOW_DESTRUCTABLE_ID
				endif
				set foregroundHeroShadow = CreateDestructable(id, localForegroundHeroX, localForegroundHeroY, 0, 1, 0)
			endif
		endif

		if localPlayerId == P then
			if not isRandom then
				call BlzFrameSetPoint( heroSelectionButtonHighlight , FRAMEPOINT_TOPLEFT , heroSelectionButton[heroIndex] , FRAMEPOINT_TOPLEFT , 0.005*0.039/MENU_BUTTON_SIZE , -0.005*0.039/MENU_BUTTON_SIZE )
				call BlzFrameSetPoint( heroSelectionButtonHighlight , FRAMEPOINT_BOTTOMRIGHT , heroSelectionButton[heroIndex] , FRAMEPOINT_BOTTOMRIGHT , -0.005*0.039/MENU_BUTTON_SIZE , 0.005*0.039/MENU_BUTTON_SIZE )
			else
				call BlzFrameSetPoint( heroSelectionButtonHighlight , FRAMEPOINT_TOPLEFT , heroSelectionButton[RANDOM_HERO] , FRAMEPOINT_TOPLEFT , 0.005*0.039/MENU_BUTTON_SIZE , -0.005*0.039/MENU_BUTTON_SIZE )
				call BlzFrameSetPoint( heroSelectionButtonHighlight , FRAMEPOINT_BOTTOMRIGHT , heroSelectionButton[RANDOM_HERO] , FRAMEPOINT_BOTTOMRIGHT , -0.005*0.039/MENU_BUTTON_SIZE , 0.005*0.039/MENU_BUTTON_SIZE )
			endif
			call BlzFrameSetVisible( heroSelectionButtonHighlight , true )
		endif

		set preselectedHeroIndex[P] = heroIndex

		call onPreselect.evaluate(whichPlayer, Hero.list[oldHero], Hero.list[heroIndex])

		call NoCrash("PreselectHero")
	endfunction

	//==========================================================================================================================================================
	//Init.
	//==========================================================================================================================================================
	
	private function InitMenu takes nothing returns nothing
		local integer i
		local integer j
		local integer jLocal
		local integer h
		local integer k
		local trigger trig
	
		local real buttonSpacing = MENU_BUTTON_SIZE + MENU_BUTTON_BUTTON_GAP
	
		local real menuWidth
		local real menuHeight
		local real Ystart
		local real currentY
		local real currentYLowest
        local real widthDiff
		local integer buttonsInRandomRow
	
		local integer column
		local integer heroesThisCategory
		local integer array whichHeroes
		local integer heroesThisCategoryLocal
		local boolean newRow
		local real xOffset
        local real glueTextOffset = 0.005

		local boolean hasNoCategoryHeroes = false
		local boolean firstCategoryThisPage

		local real pageCycleDownX
		local real pageCycleUpX
		local real pageCycleDownY
		local real pageCycleUpY
		local boolean pageCycleTextType
		local real pageCycleButtonSpacing
		local real pageCycleButtonSize
		local real pageCycleScaleOffset

        local real wideScreenAreaWidth
        local real menuXLeftLocal

        local real categoryScale

		call InitCrashCheck("InitMenu")
		
		call BlzLoadTOCFile("HeroSelectionTemplates.toc")

        set wideScreenAreaWidth = 0.6*(BlzGetLocalClientWidth()/I2R(BlzGetLocalClientHeight()) - 4.0/3.0)/2.0
        set menuXLeftLocal = RMaxBJ(0, MENU_X_LEFT + wideScreenAreaWidth)
        set tooltipLeftXLocal = RMinBJ(TOOLTIP_LEFT_X + wideScreenAreaWidth, 0.8 + wideScreenAreaWidth - TOOLTIP_WIDTH)
	
		set menuWidth = 2*MENU_LEFT_RIGHT_EDGE_GAP + MENU_NUMBER_OF_COLUMNS*MENU_BUTTON_SIZE + (MENU_NUMBER_OF_COLUMNS - 1)*MENU_BUTTON_BUTTON_GAP
		if MENU_BORDER_TILE_SIZE > 0 then
            set widthDiff = menuWidth
			set menuWidth = R2I(menuWidth/MENU_BORDER_TILE_SIZE + 0.99)*MENU_BORDER_TILE_SIZE
            set widthDiff = menuWidth - widthDiff
        else
            set widthDiff = 0
		endif
	
		set heroSelectionMenu = BlzCreateFrame("HeroSelectionMenu", BlzGetOriginFrame(ORIGIN_FRAME_WORLD_FRAME, 0), 0, 0)
		call BlzFrameSetPoint(heroSelectionMenu, FRAMEPOINT_TOPLEFT , fullScreenFrame , FRAMEPOINT_BOTTOMLEFT , menuXLeftLocal , MENU_Y_TOP )
		call BlzFrameSetPoint(heroSelectionMenu, FRAMEPOINT_BOTTOMRIGHT , fullScreenFrame , FRAMEPOINT_BOTTOMLEFT , menuXLeftLocal + menuWidth , 0 )
		
		set heroSelectionButtonTrigger = CreateTrigger()
		call TriggerAddAction( heroSelectionButtonTrigger , function PreselectHero )
	
		set Ystart = MENU_Y_TOP - MENU_TOP_EDGE_GAP
		set currentYLowest = Ystart

		//Hero buttons
		set k = 1
		loop
			exitwhen k > NUMBER_OF_PAGES

			set currentY = Ystart
			set firstCategoryThisPage = true

			set i = 0
			loop
				exitwhen i > NUMBER_OF_CATEGORIES
				if PAGE_OF_CATEGORY[i] == k or (PAGE_OF_CATEGORY[i] == 0 and k == 1) then
					if i > 0 then
						if not firstCategoryThisPage or CATEGORY_NAMES[i] != null then
							set currentY = currentY - MENU_CATEGORY_GAP
						endif
						if not firstCategoryThisPage then
							set currentY = currentY - buttonSpacing
						endif

						if CATEGORY_NAMES[i] != null then
                            set categoryScale = MENU_CATEGORY_FONT_SIZE/10.
							set heroSelectionCategory[i] = BlzCreateFrameByType("TEXT", CATEGORY_NAMES[i], heroSelectionMenu, "", 0)
							call BlzFrameSetPoint(heroSelectionCategory[i] , FRAMEPOINT_TOPLEFT , fullScreenFrame , FRAMEPOINT_BOTTOMLEFT , menuXLeftLocal/categoryScale , (currentY + MENU_CATEGORY_GAP/2 + MENU_CATEGORY_TITLE_Y + 0.02)/categoryScale)
							call BlzFrameSetPoint(heroSelectionCategory[i] , FRAMEPOINT_BOTTOMRIGHT , fullScreenFrame , FRAMEPOINT_BOTTOMLEFT , (menuXLeftLocal + menuWidth)/categoryScale , (currentY + MENU_CATEGORY_GAP/2 + MENU_CATEGORY_TITLE_Y - 0.02)/categoryScale)
							call BlzFrameSetTextAlignment(heroSelectionCategory[i] , TEXT_JUSTIFY_MIDDLE , TEXT_JUSTIFY_CENTER)
							call BlzFrameSetScale(heroSelectionCategory[i], categoryScale)
							call BlzFrameSetText(heroSelectionCategory[i] , CATEGORY_NAMES[i])

							if k != 1 then
								call BlzFrameSetVisible(heroSelectionCategory[i], false)
							endif
						endif
					endif

					set heroesThisCategory = 0
					set heroesThisCategoryLocal = 0
					set j = 1
					loop
						exitwhen j > NUMBER_OF_HEROES
						if Hero.list[j].category == i then
							set heroesThisCategory = heroesThisCategory + 1
							set whichHeroes[heroesThisCategory] = j
							if not Hero.list[j].unavailableToTeam[TEAM_OF_PLAYER[localPlayerId]] then
								set heroesThisCategoryLocal = heroesThisCategoryLocal + 1
							endif
						endif
						set j = j + 1
					endloop

					if i == 0 and heroesThisCategoryLocal > 0 then
						set hasNoCategoryHeroes = true
					endif
			
					set column = 0
					set newRow = true
					set j = 1
					set jLocal = 1
					loop
						exitwhen j > heroesThisCategory
						if newRow then
							if heroesThisCategoryLocal - (jLocal-1) < MENU_NUMBER_OF_COLUMNS then
								set xOffset = buttonSpacing/2 * (MENU_NUMBER_OF_COLUMNS - (heroesThisCategoryLocal - (jLocal-1))) + widthDiff/2
							else
								set xOffset = widthDiff/2
							endif
							if jLocal != 1 then
								set currentY = currentY - buttonSpacing
							endif
						endif
						set h = whichHeroes[j]
			
						set heroSelectionButton[h] = BlzCreateFrameByType("GLUETEXTBUTTON", "heroSelectionButton_" + I2S(h), heroSelectionMenu, "ScriptDialogButton", 0)
						call BlzFrameSetPoint( heroSelectionButton[h] , FRAMEPOINT_TOPLEFT , fullScreenFrame , FRAMEPOINT_BOTTOMLEFT , menuXLeftLocal + MENU_LEFT_RIGHT_EDGE_GAP + xOffset + column*buttonSpacing - glueTextOffset, currentY + glueTextOffset )
						call BlzFrameSetPoint( heroSelectionButton[h] , FRAMEPOINT_BOTTOMRIGHT , fullScreenFrame , FRAMEPOINT_BOTTOMLEFT , menuXLeftLocal + MENU_LEFT_RIGHT_EDGE_GAP + xOffset + column*buttonSpacing + MENU_BUTTON_SIZE + glueTextOffset, currentY - MENU_BUTTON_SIZE - glueTextOffset )
						
						call SetButtonFrames(h)
						call SetButtonTextures(h, not PRE_SELECT_BEFORE_ENABLED or Hero.list[h].unavailable)
						if BlzGetAbilityExtendedTooltip(Hero.list[h].tooltipAbility, 0) != null then
							if not Hero.list[h].unavailable then
								call SetButtonTooltip(h, Hero.list[h].name, BlzGetAbilityExtendedTooltip(Hero.list[h].tooltipAbility, 0))
							else
								call SetButtonTooltip(h, Hero.list[h].name, BlzGetAbilityExtendedTooltip(Hero.list[h].tooltipAbility, 0) + "|n|n|cffff0000Not available.|r")
							endif
						else
							static if HERO_SELECTION_ENABLE_DEBUG_MODE then
								call BJDebugMsg("|cffff0000Warning:|r Tooltip missing or invalid for hero " + Hero.list[h].name + "...")
							endif
						endif

						call BlzFrameSetVisible(heroSelectionButton[h], not Hero.list[h].unavailableToTeam[TEAM_OF_PLAYER[localPlayerId]] and k == 1)

						call BlzTriggerRegisterFrameEvent( heroSelectionButtonTrigger, heroSelectionButton[h] , FRAMEEVENT_CONTROL_CLICK )
			
						if not Hero.list[h].unavailableToTeam[TEAM_OF_PLAYER[localPlayerId]] then
							set column = column + 1
							if column == MENU_NUMBER_OF_COLUMNS then
								set newRow = true
								set column = 0
							else
								set newRow = false
							endif
							set jLocal = jLocal + 1
						endif
						set j = j + 1
					endloop

					if i > 0 or hasNoCategoryHeroes then
						set firstCategoryThisPage = false
					endif
				endif

				set i = i + 1
			endloop

			if currentY < currentYLowest then
				set currentYLowest = currentY
			endif

			set k = k + 1
		endloop

		set currentY = currentYLowest
	
		//Random
		if MENU_INCLUDE_RANDOM_PICK or MENU_INCLUDE_SUGGEST_RANDOM or (NUMBER_OF_PAGES > 1 and PAGE_CYCLE_BUTTON_STYLE == "EnvelopRandomButton") then
			set currentY = currentY - buttonSpacing - MENU_HEROES_RANDOM_GAP
			set buttonsInRandomRow = 0
			static if MENU_INCLUDE_RANDOM_PICK then
				set buttonsInRandomRow = buttonsInRandomRow + 1
			endif
			static if MENU_INCLUDE_SUGGEST_RANDOM then
				set buttonsInRandomRow = buttonsInRandomRow + 1
			endif
			if (NUMBER_OF_PAGES > 1 and PAGE_CYCLE_BUTTON_STYLE == "EnvelopRandomButton") then
				set buttonsInRandomRow = buttonsInRandomRow + 2
			endif

			if HERO_SELECTION_ENABLE_DEBUG_MODE and MENU_NUMBER_OF_COLUMNS < buttonsInRandomRow then
				call BJDebugMsg("|cffff0000Warning:|r Not enough columns set to accomodate all buttons in the random row...")
			endif
		endif

		static if MENU_INCLUDE_RANDOM_PICK then

			if MENU_INCLUDE_SUGGEST_RANDOM then
				set xOffset = buttonSpacing/2 * (MENU_NUMBER_OF_COLUMNS - 2) + widthDiff/2
			else
				set xOffset = buttonSpacing/2 * (MENU_NUMBER_OF_COLUMNS - 1) + widthDiff/2
			endif
		
			set heroSelectionButton[RANDOM_HERO] = BlzCreateFrameByType("GLUETEXTBUTTON", "heroRandomButton" , heroSelectionMenu, "ScriptDialogButton", 0)
			call BlzFrameSetPoint( heroSelectionButton[RANDOM_HERO] , FRAMEPOINT_TOPLEFT , fullScreenFrame , FRAMEPOINT_BOTTOMLEFT , menuXLeftLocal + MENU_LEFT_RIGHT_EDGE_GAP + xOffset - glueTextOffset, currentY + glueTextOffset )
			call BlzFrameSetPoint( heroSelectionButton[RANDOM_HERO] , FRAMEPOINT_BOTTOMRIGHT , fullScreenFrame , FRAMEPOINT_BOTTOMLEFT , menuXLeftLocal + MENU_LEFT_RIGHT_EDGE_GAP + xOffset + MENU_BUTTON_SIZE + glueTextOffset, currentY - MENU_BUTTON_SIZE - glueTextOffset )

			call SetButtonFrames(RANDOM_HERO)
			call SetButtonTextures(RANDOM_HERO, not PRE_SELECT_BEFORE_ENABLED)
			call SetButtonTooltip(RANDOM_HERO, "Select Random", RANDOM_HERO_TOOLTIP)
			
			call BlzTriggerRegisterFrameEvent( heroSelectionButtonTrigger, heroSelectionButton[RANDOM_HERO] , FRAMEEVENT_CONTROL_CLICK )
		endif

		//Suggest
		static if MENU_INCLUDE_SUGGEST_RANDOM then

			if MENU_INCLUDE_RANDOM_PICK then
				set xOffset = buttonSpacing/2 * MENU_NUMBER_OF_COLUMNS + widthDiff/2
			else
				set xOffset = buttonSpacing/2 * (MENU_NUMBER_OF_COLUMNS - 1) + widthDiff/2
			endif
		
			set heroSelectionButton[SUGGEST_RANDOM] = BlzCreateFrameByType("GLUETEXTBUTTON", "heroRandomButton" , heroSelectionMenu, "ScriptDialogButton", 0)
			call BlzFrameSetPoint( heroSelectionButton[SUGGEST_RANDOM] , FRAMEPOINT_TOPLEFT , fullScreenFrame , FRAMEPOINT_BOTTOMLEFT , menuXLeftLocal + MENU_LEFT_RIGHT_EDGE_GAP + xOffset - glueTextOffset, currentY + glueTextOffset )
			call BlzFrameSetPoint( heroSelectionButton[SUGGEST_RANDOM] , FRAMEPOINT_BOTTOMRIGHT , fullScreenFrame , FRAMEPOINT_BOTTOMLEFT , menuXLeftLocal + MENU_LEFT_RIGHT_EDGE_GAP + xOffset + MENU_BUTTON_SIZE + glueTextOffset, currentY - MENU_BUTTON_SIZE - glueTextOffset )
			
			call SetButtonFrames(SUGGEST_RANDOM)
			call SetButtonTextures(SUGGEST_RANDOM, not PRE_SELECT_BEFORE_ENABLED)
			call SetButtonTooltip(SUGGEST_RANDOM, "Suggest Random", SUGGEST_RANDOM_TOOLTIP)

			call BlzTriggerRegisterFrameEvent( heroSelectionButtonTrigger, heroSelectionButton[SUGGEST_RANDOM] , FRAMEEVENT_CONTROL_CLICK )
		endif

		//Set Bottom Corners
		set menuHeight = MENU_Y_TOP - (currentY - buttonSpacing - MENU_BOTTOM_EDGE_GAP)
		if MENU_BORDER_TILE_SIZE > 0 then
			set menuHeight = R2I(menuHeight/MENU_BORDER_TILE_SIZE + 0.99)*MENU_BORDER_TILE_SIZE
		endif

		call BlzFrameSetPoint(heroSelectionMenu, FRAMEPOINT_BOTTOMRIGHT , fullScreenFrame , FRAMEPOINT_BOTTOMLEFT , menuXLeftLocal + menuWidth , MENU_Y_TOP - menuHeight )

		//Page Cycle Buttons
		if NUMBER_OF_PAGES > 1 then
			set pageCycleTrigger = CreateTrigger()
			call TriggerAddAction(pageCycleTrigger, function CyclePage)
			set pageCycleButtonSpacing = buttonSpacing * MENU_PAGE_CYCLE_SCALE
			set pageCycleButtonSize = MENU_BUTTON_SIZE * MENU_PAGE_CYCLE_SCALE
			set pageCycleScaleOffset = (buttonSpacing - pageCycleButtonSpacing)/2

			if PAGE_CYCLE_BUTTON_STYLE == "EnvelopRandomButton" then
				set pageCycleDownX = menuXLeftLocal + MENU_LEFT_RIGHT_EDGE_GAP + buttonSpacing/2*(MENU_NUMBER_OF_COLUMNS - buttonsInRandomRow) + widthDiff/2
				set pageCycleUpX = menuXLeftLocal + MENU_LEFT_RIGHT_EDGE_GAP + buttonSpacing/2*(MENU_NUMBER_OF_COLUMNS + buttonsInRandomRow - 2) + widthDiff/2
				set pageCycleDownY = currentY
				set pageCycleUpY = currentY
				set pageCycleTextType = false
			elseif PAGE_CYCLE_BUTTON_STYLE == "LeftRightMenuButton" then
				set pageCycleDownX = menuXLeftLocal + MENU_LEFT_RIGHT_EDGE_GAP + widthDiff/2
				set pageCycleUpX = menuXLeftLocal + MENU_LEFT_RIGHT_EDGE_GAP + pageCycleButtonSpacing*(MENU_NUMBER_OF_COLUMNS - 1) + widthDiff/2
				set pageCycleDownY = currentY
				set pageCycleUpY = currentY
				set pageCycleTextType = false
			elseif PAGE_CYCLE_BUTTON_STYLE == "BelowRandomButton" then
				set pageCycleDownX = MENU_LEFT_RIGHT_EDGE_GAP + buttonSpacing/2*(MENU_NUMBER_OF_COLUMNS - 2) + widthDiff/2
				set pageCycleUpX = MENU_LEFT_RIGHT_EDGE_GAP + buttonSpacing/2*MENU_NUMBER_OF_COLUMNS + widthDiff/2
				set currentY = currentY - pageCycleButtonSpacing - MENU_HEROES_RANDOM_GAP
				set pageCycleDownY = currentY
				set pageCycleUpY = currentY
				set pageCycleTextType = false

				set menuHeight = MENU_Y_TOP - (currentY - buttonSpacing - MENU_BOTTOM_EDGE_GAP)
				if MENU_BORDER_TILE_SIZE > 0 then
					set menuHeight = R2I(menuHeight/MENU_BORDER_TILE_SIZE + 0.99)*MENU_BORDER_TILE_SIZE
				endif
				call BlzFrameSetPoint(heroSelectionMenu, FRAMEPOINT_BOTTOMRIGHT , fullScreenFrame , FRAMEPOINT_BOTTOMLEFT , menuXLeftLocal + menuWidth , MENU_Y_TOP - menuHeight )

			elseif PAGE_CYCLE_BUTTON_STYLE == "LeftRightMiddleButton" then
				set pageCycleDownX = menuXLeftLocal - pageCycleButtonSize/2
				set pageCycleUpX = menuXLeftLocal + menuWidth - pageCycleButtonSize/2
				set pageCycleDownY = MENU_Y_TOP - (menuHeight - pageCycleButtonSize)/2
				set pageCycleUpY = pageCycleDownY
				set pageCycleTextType = false
			elseif PAGE_CYCLE_BUTTON_STYLE == "RightVerticalButton" then
				set pageCycleDownX = menuXLeftLocal + menuWidth - pageCycleButtonSize/2
				set pageCycleUpX = menuXLeftLocal + menuWidth - pageCycleButtonSize/2
				set pageCycleDownY = MENU_Y_TOP - menuHeight/2
				set pageCycleUpY = MENU_Y_TOP - menuHeight/2 + pageCycleButtonSpacing
				set pageCycleTextType = false
			elseif PAGE_CYCLE_BUTTON_STYLE == "EnvelopAcceptButton" then
				set pageCycleDownX = menuXLeftLocal + menuWidth/2 - MENU_PAGE_CYCLE_SCALE*SELECT_BUTTON_WIDTH - SELECT_BUTTON_WIDTH/2
				set pageCycleUpX = menuXLeftLocal + menuWidth/2 + SELECT_BUTTON_WIDTH/2
				set pageCycleDownY = MENU_Y_TOP - menuHeight
				set pageCycleUpY = pageCycleDownY
				set pageCycleTextType = true
			elseif PAGE_CYCLE_BUTTON_STYLE == "LeftRightBottomButton" then
				set pageCycleDownX = menuXLeftLocal
				set pageCycleUpX = menuXLeftLocal + menuWidth - SELECT_BUTTON_WIDTH*MENU_PAGE_CYCLE_SCALE
				set pageCycleDownY = MENU_Y_TOP - menuHeight
				set pageCycleUpY = pageCycleDownY
				set pageCycleTextType = true
			elseif PAGE_CYCLE_BUTTON_STYLE == "TangentTopButton" then
				set pageCycleDownX = menuXLeftLocal + menuWidth/2 - SELECT_BUTTON_WIDTH*MENU_PAGE_CYCLE_SCALE
				set pageCycleUpX = menuXLeftLocal + menuWidth/2
				set pageCycleDownY = MENU_Y_TOP - 0.0175
				set pageCycleUpY = pageCycleDownY
				set pageCycleTextType = true
			elseif PAGE_CYCLE_BUTTON_STYLE == "LeftRightTopButton" then
				set pageCycleDownX = menuXLeftLocal
				set pageCycleUpX = menuXLeftLocal + menuWidth - SELECT_BUTTON_WIDTH*MENU_PAGE_CYCLE_SCALE
				set pageCycleDownY = MENU_Y_TOP - 0.0175
				set pageCycleUpY = pageCycleDownY
				set pageCycleTextType = true
			elseif HERO_SELECTION_ENABLE_DEBUG_MODE then
				call BJDebugMsg("|cffff0000Warning:|r Unrecognized page cycle button style (" + PAGE_CYCLE_BUTTON_STYLE + ").")
			endif

			if pageCycleTextType then
				set heroSelectionButton[PAGE_DOWN] = BlzCreateFrameByType("GLUETEXTBUTTON", "pageDownButton" , heroSelectionMenu, "ScriptDialogButton", 0)
				call BlzFrameSetPoint( heroSelectionButton[PAGE_DOWN] , FRAMEPOINT_TOPLEFT , fullScreenFrame , FRAMEPOINT_BOTTOMLEFT , pageCycleDownX + MENU_PAGE_CYCLE_X_OFFSET , pageCycleDownY + 0.012 + 0.012*SELECT_BUTTON_SCALE*MENU_PAGE_CYCLE_SCALE + MENU_PAGE_CYCLE_Y_OFFSET )
				call BlzFrameSetPoint( heroSelectionButton[PAGE_DOWN] , FRAMEPOINT_BOTTOMRIGHT , fullScreenFrame , FRAMEPOINT_BOTTOMLEFT , pageCycleDownX + MENU_PAGE_CYCLE_X_OFFSET + SELECT_BUTTON_WIDTH*MENU_PAGE_CYCLE_SCALE , pageCycleDownY - 0.003 - 0.003*SELECT_BUTTON_SCALE*MENU_PAGE_CYCLE_SCALE + MENU_PAGE_CYCLE_Y_OFFSET )
				call BlzFrameSetText( heroSelectionButton[PAGE_DOWN] , "Prev" )
				call BlzFrameSetScale( heroSelectionButton[PAGE_DOWN] , SELECT_BUTTON_SCALE*MENU_PAGE_CYCLE_SCALE )
			else
				set heroSelectionButton[PAGE_DOWN] = BlzCreateFrameByType("GLUETEXTBUTTON", "pageDownButton" , heroSelectionMenu, "ScriptDialogButton", 0)
				call BlzFrameSetPoint( heroSelectionButton[PAGE_DOWN] , FRAMEPOINT_TOPLEFT , fullScreenFrame , FRAMEPOINT_BOTTOMLEFT , pageCycleDownX + MENU_PAGE_CYCLE_X_OFFSET - glueTextOffset + pageCycleScaleOffset, pageCycleDownY + glueTextOffset + MENU_PAGE_CYCLE_Y_OFFSET - pageCycleScaleOffset )
				call BlzFrameSetPoint( heroSelectionButton[PAGE_DOWN] , FRAMEPOINT_BOTTOMRIGHT , fullScreenFrame , FRAMEPOINT_BOTTOMLEFT , pageCycleDownX + MENU_PAGE_CYCLE_X_OFFSET + pageCycleButtonSize + glueTextOffset + pageCycleScaleOffset, pageCycleDownY - pageCycleButtonSize - glueTextOffset + MENU_PAGE_CYCLE_Y_OFFSET - pageCycleScaleOffset )
				call SetButtonFrames(PAGE_DOWN)
				call SetButtonTextures(PAGE_DOWN, not PRE_SELECT_BEFORE_ENABLED)
				call SetButtonTooltip(PAGE_DOWN, "Page Down", "Go to the previous page.")
			endif

			call BlzTriggerRegisterFrameEvent( pageCycleTrigger, heroSelectionButton[PAGE_DOWN] , FRAMEEVENT_CONTROL_CLICK )

			if pageCycleTextType then
				set heroSelectionButton[PAGE_UP] = BlzCreateFrameByType("GLUETEXTBUTTON", "pageUpButton" , heroSelectionMenu, "ScriptDialogButton", 0)
				call BlzFrameSetPoint( heroSelectionButton[PAGE_UP] , FRAMEPOINT_TOPLEFT , fullScreenFrame , FRAMEPOINT_BOTTOMLEFT , pageCycleUpX - MENU_PAGE_CYCLE_X_OFFSET , pageCycleUpY + 0.012 + 0.012*SELECT_BUTTON_SCALE*MENU_PAGE_CYCLE_SCALE + MENU_PAGE_CYCLE_Y_OFFSET )
				call BlzFrameSetPoint( heroSelectionButton[PAGE_UP] , FRAMEPOINT_BOTTOMRIGHT , fullScreenFrame , FRAMEPOINT_BOTTOMLEFT , pageCycleUpX - MENU_PAGE_CYCLE_X_OFFSET + SELECT_BUTTON_WIDTH*MENU_PAGE_CYCLE_SCALE , pageCycleUpY - 0.003 - 0.003*SELECT_BUTTON_SCALE*MENU_PAGE_CYCLE_SCALE + MENU_PAGE_CYCLE_Y_OFFSET )
				call BlzFrameSetText( heroSelectionButton[PAGE_UP] , "Next" )
				call BlzFrameSetScale( heroSelectionButton[PAGE_UP] , SELECT_BUTTON_SCALE*MENU_PAGE_CYCLE_SCALE )
			else
				set heroSelectionButton[PAGE_UP] = BlzCreateFrameByType("GLUETEXTBUTTON", "pageUpButton" , heroSelectionMenu, "ScriptDialogButton", 0)
				call BlzFrameSetPoint( heroSelectionButton[PAGE_UP] , FRAMEPOINT_TOPLEFT , fullScreenFrame , FRAMEPOINT_BOTTOMLEFT , pageCycleUpX - MENU_PAGE_CYCLE_X_OFFSET - glueTextOffset + pageCycleScaleOffset , pageCycleUpY + glueTextOffset + MENU_PAGE_CYCLE_Y_OFFSET - pageCycleScaleOffset )
				call BlzFrameSetPoint( heroSelectionButton[PAGE_UP] , FRAMEPOINT_BOTTOMRIGHT , fullScreenFrame , FRAMEPOINT_BOTTOMLEFT , pageCycleUpX - MENU_PAGE_CYCLE_X_OFFSET + pageCycleButtonSize + glueTextOffset + pageCycleScaleOffset , pageCycleUpY - pageCycleButtonSize - glueTextOffset + MENU_PAGE_CYCLE_Y_OFFSET - pageCycleScaleOffset )
				call SetButtonFrames(PAGE_UP)
				call SetButtonTextures(PAGE_UP, not PRE_SELECT_BEFORE_ENABLED)
				call SetButtonTooltip(PAGE_UP, "Page Up", "Go to the next page.")
			endif

			call BlzTriggerRegisterFrameEvent( pageCycleTrigger, heroSelectionButton[PAGE_UP] , FRAMEEVENT_CONTROL_CLICK )
		endif

		//Highlight
		set heroSelectionButtonHighlight =  BlzCreateFrameByType("SPRITE", "SpriteName", heroSelectionMenu, "", 0)
		call BlzFrameSetModel(heroSelectionButtonHighlight, "UI\\Feedback\\Autocast\\UI-ModalButtonOn.mdl", 0)
		call BlzFrameSetScale(heroSelectionButtonHighlight, MENU_BUTTON_SIZE/0.039)
		call BlzFrameSetVisible( heroSelectionButtonHighlight , false )

		//Accept
		set heroAcceptButton = BlzCreateFrameByType("GLUETEXTBUTTON", "heroAcceptButton" , heroSelectionMenu, "ScriptDialogButton", 0)
		call BlzFrameSetPoint( heroAcceptButton , FRAMEPOINT_TOPLEFT , fullScreenFrame , FRAMEPOINT_BOTTOMLEFT , menuXLeftLocal + menuWidth/2 - SELECT_BUTTON_WIDTH/2 , MENU_Y_TOP - menuHeight + 0.012 + 0.012*SELECT_BUTTON_SCALE )
		call BlzFrameSetPoint( heroAcceptButton , FRAMEPOINT_BOTTOMRIGHT , fullScreenFrame , FRAMEPOINT_BOTTOMLEFT , menuXLeftLocal + menuWidth/2 + SELECT_BUTTON_WIDTH/2 , MENU_Y_TOP - menuHeight - 0.003 - 0.003*SELECT_BUTTON_SCALE )
		call BlzFrameSetText( heroAcceptButton , SELECT_BUTTON_TEXT )
		call BlzFrameSetScale( heroAcceptButton , SELECT_BUTTON_SCALE )
		call BlzFrameSetEnable( heroAcceptButton , false )

		set trig = CreateTrigger()
		call BlzTriggerRegisterFrameEvent( trig , heroAcceptButton , FRAMEEVENT_CONTROL_CLICK )
		call TriggerAddAction( trig, function PickHero )
		set trig = null

		//Ban
		set heroBanButton = BlzCreateFrameByType("GLUETEXTBUTTON", "heroBanButton" , heroSelectionMenu, "ScriptDialogButton", 0)
		call BlzFrameSetPoint( heroBanButton , FRAMEPOINT_TOPLEFT , fullScreenFrame , FRAMEPOINT_BOTTOMLEFT , menuXLeftLocal + menuWidth/2 - SELECT_BUTTON_WIDTH/2 , MENU_Y_TOP - menuHeight + 0.012 + 0.012*SELECT_BUTTON_SCALE )
		call BlzFrameSetPoint( heroBanButton , FRAMEPOINT_BOTTOMRIGHT , fullScreenFrame , FRAMEPOINT_BOTTOMLEFT , menuXLeftLocal + menuWidth/2 + SELECT_BUTTON_WIDTH/2 , MENU_Y_TOP - menuHeight - 0.003 - 0.003*SELECT_BUTTON_SCALE )
		call BlzFrameSetText( heroBanButton , SELECT_BUTTON_TEXT )
		call BlzFrameSetText( heroBanButton , "Ban" )
		call BlzFrameSetEnable( heroBanButton , false )
		call BlzFrameSetVisible( heroBanButton , false)

		set trig = CreateTrigger()
		call BlzTriggerRegisterFrameEvent( trig , heroBanButton , FRAMEEVENT_CONTROL_CLICK )
		call TriggerAddAction( trig, function BanHero )
		
		//Ability Buttons
		set i = 1
		loop
		exitwhen i > NUMBER_OF_ABILITY_FRAMES
			set heroSelectionAbility[i] = BlzCreateFrameByType("BACKDROP", "heroSelectionAbility_" + I2S(i), heroSelectionMenu, "", 0)
			static if ABILITY_BUTTON_HORIZONTAL_LAYOUT then
				call BlzFrameSetPoint(heroSelectionAbility[i], FRAMEPOINT_TOPLEFT, heroSelectionMenu , FRAMEPOINT_TOPRIGHT, HERO_ABILITY_PREVIEW_BUTTON_X + HERO_ABILITY_PREVIEW_BUTTON_SIZE*(i-1), HERO_ABILITY_PREVIEW_BUTTON_Y)
			else
				call BlzFrameSetPoint(heroSelectionAbility[i], FRAMEPOINT_TOPLEFT, heroSelectionMenu , FRAMEPOINT_TOPRIGHT, HERO_ABILITY_PREVIEW_BUTTON_X, HERO_ABILITY_PREVIEW_BUTTON_Y - HERO_ABILITY_PREVIEW_BUTTON_SIZE*(i-1))
			endif
			call BlzFrameSetSize(heroSelectionAbility[i], HERO_ABILITY_PREVIEW_BUTTON_SIZE, HERO_ABILITY_PREVIEW_BUTTON_SIZE)
			call BlzFrameSetVisible( heroSelectionAbility[i] , false )
			
			set heroSelectionAbilityHover[i] = BlzCreateFrameByType("FRAME" , "heroIconFrameHover" , heroSelectionAbility[i] , "" , 0)
			call BlzFrameSetAllPoints( heroSelectionAbilityHover[i] , heroSelectionAbility[i] )
	
			set heroSelectionAbilityTooltip[i] = BlzCreateFrame("CustomTooltip", heroSelectionAbilityHover[i], 0, 0)
			if TOOLTIP_LOCK_TOP then
				call BlzFrameSetPoint( heroSelectionAbilityTooltip[i] , FRAMEPOINT_TOPLEFT , fullScreenFrame , FRAMEPOINT_BOTTOMLEFT , tooltipLeftXLocal , TOOLTIP_Y )
			else
				call BlzFrameSetPoint( heroSelectionAbilityTooltip[i] , FRAMEPOINT_BOTTOMLEFT , fullScreenFrame , FRAMEPOINT_BOTTOMLEFT , tooltipLeftXLocal , TOOLTIP_Y )
			endif
			call BlzFrameSetTooltip( heroSelectionAbilityHover[i] , heroSelectionAbilityTooltip[i] )
			call BlzFrameSetSize( heroSelectionAbilityTooltip[i] , TOOLTIP_WIDTH , 0.0 )
			set heroSelectionAbilityTooltipTitle[i] = BlzFrameGetChild( heroSelectionAbilityTooltip[i],0)
			set heroSelectionAbilityTooltipText[i] = BlzFrameGetChild( heroSelectionAbilityTooltip[i],1)
			
			set i = i + 1
		endloop
		
		call BlzFrameSetVisible( heroSelectionMenu, false)
		call NoCrash("InitMenu")
	endfunction

	//==========================================================================================================================================================
	//API
	//==========================================================================================================================================================

	function EveryoneHasHero takes nothing returns boolean
		return numPlayersWithHero == numSelectingPlayers
	endfunction

	function NoOneInHeroSelection takes nothing returns boolean
		return numPlayersInSelection == 0
	endfunction

	function EnablePlayerHeroSelection takes player whichPlayer, boolean enable returns nothing
		local integer i
		local integer P = GetPlayerId(whichPlayer)

		if not isInHeroSelection[P] then
			static if HERO_SELECTION_ENABLE_DEBUG_MODE then
				call BJDebugMsg("|cffff0000Warning:|r Attempted to enable hero selection for player who is not in hero selection...")
			endif
			return
		endif

		call InitCrashCheck("EnablePlayerHeroSelection")
		
		if enable then
			if localPlayerId == P then
				call BlzFrameSetVisible(heroSelectionMenu, true)
				set i = 1
				loop
				exitwhen i > PAGE_UP
					call SetButtonTextures(i, false)
					set i = i + 1
				endloop
			endif
			static if PHANTOM_HERO_WHEN_CANNOT_BE_PICKED then
				call BlzSetSpecialEffectColor( selectedHero[localPlayerId] , 255 , 255 , 255 )
				call BlzSetSpecialEffectAlpha( selectedHero[localPlayerId] , 255 )
			endif
		else
			if localPlayerId == P then
				set i = 1
				loop
				exitwhen i > NUMBER_OF_ABILITY_FRAMES
					call BlzFrameSetVisible( heroSelectionAbility[i] , false )
					set i = i + 1
				endloop
			
				set i = 1
				loop
				exitwhen i > PAGE_UP
					call SetButtonTextures(i, true)
					set i = i + 1
				endloop

				call BlzFrameSetEnable(heroAcceptButton, false)
			endif
			
			static if PHANTOM_HERO_WHEN_CANNOT_BE_PICKED then
				call BlzSetSpecialEffectColor( selectedHero[localPlayerId] , 0 , 0 , 0 )
				call BlzSetSpecialEffectAlpha( selectedHero[localPlayerId] , 128 )
			endif
			set preselectedHeroIndex[P] = 0
		endif
		set heroSelectionDisabledForPlayer[P] = not enable

		call NoCrash("EnablePlayerHeroSelection")
	endfunction

	function EnablePlayerHeroPreselection takes player whichPlayer, boolean enable returns nothing
		local integer i
		local integer P = GetPlayerId(whichPlayer)

		if not isInHeroSelection[P] then
			static if HERO_SELECTION_ENABLE_DEBUG_MODE then
				call BJDebugMsg("|cffff0000Warning:|r Attempted to enable hero preselection for player who is not in hero selection...")
			endif
			return
		endif

		call InitCrashCheck("EnablePlayerHeroPreselection")
		
		if enable then
			if GetLocalPlayer() == whichPlayer then
				set i = 1
				loop
				exitwhen i > PAGE_UP
					call SetButtonTextures(i, false)
					set i = i + 1
				endloop
			endif
		else
			if GetLocalPlayer() == whichPlayer then
				set i = 1
				loop
				exitwhen i > NUMBER_OF_ABILITY_FRAMES
					call BlzFrameSetVisible( heroSelectionAbility[i] , false )
					set i = i + 1
				endloop
			
				set i = 1
				loop
				exitwhen i > PAGE_UP
					call SetButtonTextures(i, true)
					set i = i + 1
				endloop

				call BlzFrameSetVisible( heroSelectionButtonHighlight, false)
				call BlzFrameSetEnable(heroAcceptButton, false)
			endif
			
			call BlzSetSpecialEffectPosition(selectedHero[P], GARBAGE_DUMP_X, GARBAGE_DUMP_Y, 0)
			call DestroyEffect(selectedHero[P])
			call DestroyEffect(selectedHeroGlow[P])
			set preselectedHeroIndex[P] = 0
		endif
		set heroPreselectionDisabledForPlayer[P] = not enable

		call NoCrash("EnablePlayerHeroPreselection")
	endfunction

	function HeroSelectionAllRandom takes nothing returns nothing
		local integer i = 1
		local integer P
		local noArgCallback onAllRandom = HeroSelectionAllRandom

		if numPlayersInSelection == 0 then
			static if HERO_SELECTION_ENABLE_DEBUG_MODE then
				call BJDebugMsg("|cffff0000Warning:|r Attempted to start hero ban phase, but no one is in hero selection right now...")
			endif
			return
		endif
		set storeHeroIndex = RANDOM_HERO
		set isForcedSelect = true
		loop
		exitwhen i > numSelectingPlayers
			set P = playerNumberOfSlot[i]
			if not playerHasHero[P] then
				set storePlayerIndex = P
				call PickHero()
			endif
			set i = i + 1
		endloop
		call onAllRandom.evaluate()
		set isForcedSelect = false
	endfunction

	function HeroSelectionPlayerForceSelect takes player whichPlayer, Hero whichHero returns nothing
		if not isInHeroSelection[GetPlayerId(whichPlayer)] then
			static if HERO_SELECTION_ENABLE_DEBUG_MODE then
				call BJDebugMsg("|cffff0000Warning:|r Attempted to force select on a player who is not in hero selection...")
			endif
			return
		endif
		if playerHasHero[GetPlayerId(whichPlayer)] then
			static if HERO_SELECTION_ENABLE_DEBUG_MODE then
				call BJDebugMsg("|cffff0000Warning:|r Attempted to force select on a player who already selected a hero...")
			endif
			return
		endif
		set isForcedSelect = true
		set storePlayerIndex = GetPlayerId(whichPlayer)
		set storeHeroIndex = whichHero.index
		call PickHero()
		set isForcedSelect = false
	endfunction

	function HeroSelectionPlayerForceRandom takes player whichPlayer returns nothing
		if not isInHeroSelection[GetPlayerId(whichPlayer)] then
			static if HERO_SELECTION_ENABLE_DEBUG_MODE then
				call BJDebugMsg("|cffff0000Warning:|r Attempted to force random on a player who is not in hero selection...")
			endif
			return
		endif
		if playerHasHero[GetPlayerId(whichPlayer)] then
			static if HERO_SELECTION_ENABLE_DEBUG_MODE then
				call BJDebugMsg("|cffff0000Warning:|r Attempted to force random on a player who already selected a hero...")
			endif
			return
		endif
		set isForcedSelect = true
		set storePlayerIndex = GetPlayerId(whichPlayer)
		set storeHeroIndex = RANDOM_HERO
		call PickHero()
		set isForcedSelect = false
	endfunction

	function HeroSelectionForceEnd takes nothing returns nothing
		local integer i = 1
		local integer P
		loop
			exitwhen i > numSelectingPlayers
			set P = playerNumberOfSlot[i]
			if not playerHasHero[P] then
				call HeroSelectionPlayerForceRandom(Player(P))
			endif
			set i = i + 1
		endloop
	endfunction

	function PlayerEscapeHeroSelection takes player whichPlayer returns nothing
		if not isInHeroSelection[GetPlayerId(whichPlayer)] then
			return
		endif
		call EscapePlayer(whichPlayer)
	endfunction

	function BanHeroFromSelection takes Hero whichHero, boolean disable returns nothing
		call ExecuteBan(whichHero, disable)
	endfunction

	function HeroSelectionSetTimeRemaining takes real time returns nothing
		call TimerStart(countdownTimer, time, false, function TimeExpires)
		call BlzFrameSetText(timerFrame, TIMER_TEXT + GetClockString(TimerGetRemaining(countdownTimer)))
		call BlzFrameSetVisible(timerFrame, isInHeroSelection[localPlayerId])
	endfunction

	function HeroSelectionAddTimeRemaining takes real time returns nothing
		call TimerStart(countdownTimer, TimerGetRemaining(countdownTimer) + time, false, function TimeExpires)
		call BlzFrameSetText(timerFrame, TIMER_TEXT + GetClockString(TimerGetRemaining(countdownTimer)))
		call BlzFrameSetVisible(timerFrame, isInHeroSelection[localPlayerId])
	endfunction
	
	function EnableHeroSelection takes nothing returns nothing
		local integer i
		local integer P
		local noArgCallback onEnable = HeroSelectionOnEnable

		if numPlayersInSelection == 0 then
			static if HERO_SELECTION_ENABLE_DEBUG_MODE then
				call BJDebugMsg("|cffff0000Warning:|r Attempted to enable hero selection, but no one is in hero selection right now...")
			endif
			return
		endif

		call InitCrashCheck("EnableHeroSelection")

		if TIME_LIMIT != 0 then
			call BlzFrameSetVisible(timerFrame, isInHeroSelection[localPlayerId])
			call TimerStart(countdownTimer, TIME_LIMIT, false, function TimeExpires)
			call TimerStart(countdownUpdateTimer, 1.0, true, function UpdateTimerFrame)
			call BlzFrameSetText(timerFrame, TIMER_TEXT + GetClockString(TimerGetRemaining(countdownTimer)))
		endif

		set i = 1
		loop
			exitwhen i > numSelectingPlayers
			set P = playerNumberOfSlot[i]
			if isInHeroSelection[P] then
				set heroSelectionDisabledForPlayer[P] = false
				set heroPreselectionDisabledForPlayer[P] = false
				set playerHasBan[P] = false
				if localPlayerId == P then
					call BlzFrameSetVisible(heroSelectionMenu, true)
				endif
			endif
            static if PLAYER_TEXT_TAGS then
                set backgroundHeroTextTag[P] = CreateTextTag()
                call SetTextTagText(backgroundHeroTextTag[P], coloredPlayerName[P] , 0.023)
                call SetTextTagPos(backgroundHeroTextTag[P], BACKGROUND_HERO_X[P], BACKGROUND_HERO_Y[P], 25)
                call SetTextTagVisibility(backgroundHeroTextTag[P], true)
            endif
			set i = i + 1
		endloop

		call BlzFrameSetVisible(heroAcceptButton, true )
		call BlzFrameSetVisible(heroBanButton, false )
		call BlzFrameSetEnable( heroBanButton, false )
		
		set captionAlphaMultiplier = 1
		if HERO_SELECTION_CAPTION != null then
			call BlzFrameSetVisible(captionFrame, isInHeroSelection[localPlayerId])
			if CAPTION_COLOR_1 != CAPTION_COLOR_2 or CAPTION_ALPHA_1 != CAPTION_ALPHA_2 then
				call AnimateCaption()
			endif
		endif
		
		if HeroCanBePicked(preselectedHeroIndex[localPlayerId]) then
			call BlzFrameSetEnable( heroAcceptButton, true )
			static if PHANTOM_HERO_WHEN_CANNOT_BE_PICKED then
				call BlzSetSpecialEffectColor( selectedHero[localPlayerId] , 255 , 255 , 255 )
				call BlzSetSpecialEffectAlpha( selectedHero[localPlayerId] , 255 )
			endif
		endif

		set inBanPhase = false
		
		set i = 1
		loop
		exitwhen i > NUMBER_OF_HEROES
			call SetButtonTextures(i, not HeroCanBePicked(i))
			set i = i + 1
		endloop
		
		call SetButtonTextures(RANDOM_HERO, false)
		call SetButtonTextures(SUGGEST_RANDOM, false)
		call SetButtonTextures(PAGE_DOWN, false)
		call SetButtonTextures(PAGE_UP, false)

		call onEnable.evaluate()

		call NoCrash("EnableHeroSelection")
	endfunction

	function StartHeroSelectionTimer takes real timeout, code callback returns nothing
		call TimerStart( countdownTimer, timeout, false, callback)
		call BlzFrameSetVisible(timerFrame, isInHeroSelection[localPlayerId])
		call TimerStart(countdownUpdateTimer, 1.0, true, function UpdateTimerFrame)
		call BlzFrameSetText(timerFrame, TIMER_TEXT + GetClockString(TimerGetRemaining(countdownTimer)))
	endfunction

	function StartHeroBanPhase takes real timeLimit returns nothing
		local integer i = 1
		local integer P

		if numPlayersInSelection == 0 then
			static if HERO_SELECTION_ENABLE_DEBUG_MODE then
				call BJDebugMsg("|cffff0000Warning:|r Attempted to start hero ban phase, but no one is in hero selection right now...")
			endif
			return
		endif

		call InitCrashCheck("StartHeroBanPhase")

		loop
			exitwhen i > numSelectingPlayers
			set P = playerNumberOfSlot[i]
			set playerHasBan[P] = true
			set heroPreselectionDisabledForPlayer[P] = false
			if localPlayerId == P then
				call BlzFrameSetVisible(heroSelectionMenu, true)
			endif
			set i = i + 1
		endloop

		set inBanPhase = true

		set i = 1
		loop
		exitwhen i > NUMBER_OF_HEROES
			call SetButtonTextures(i, not HeroCanBePicked(i))
			set i = i + 1
		endloop

		call SetButtonTextures(SUGGEST_RANDOM, false)
		call SetButtonTextures(PAGE_DOWN, false)
		call SetButtonTextures(PAGE_UP, false)

		call BlzFrameSetVisible(heroAcceptButton, false)
		call BlzFrameSetVisible(heroBanButton, true)

		call BlzFrameSetEnable( heroBanButton , HeroCanBePicked(preselectedHeroIndex[localPlayerId]) and preselectedHeroIndex[P] != RANDOM_HERO )
		call StartHeroSelectionTimer(timeLimit, function EnableHeroSelection)

		call NoCrash("StartHeroBanPhase")
	endfunction

	function PlayerReturnToHeroSelection takes player whichPlayer returns nothing
		local integer i
		local integer P = GetPlayerId(whichPlayer)
		local integer id
		local playerCallback onReturn = HeroSelectionOnReturn
        local framehandle consoleUIBackdrop = BlzGetFrameByName("ConsoleUIBackdrop",0)

		if heroSelectionMenu == null then
			static if HERO_SELECTION_ENABLE_DEBUG_MODE then
				call BJDebugMsg("|cffff0000Warning:|r Attempted to return player to hero selection, but hero selection was not initialized...")
			endif
			return
		endif

		call InitCrashCheck("PlayerReturnToHeroSelection")

		set playerHasHero[P] = false
		set isInHeroSelection[P] = true
		set isRepicking[P] = true
		set numPlayersWithHero = numPlayersWithHero - 1
		set numPlayersInSelection = numPlayersInSelection + 1
		set heroIndexWasPicked[pickedHeroIndex[P]] = false
		set pickedHeroIndex[P] = 0

		static if not DELETE_BACKGROUND_HEROES_AFTER_END then
			call BlzSetSpecialEffectPosition(backgroundHero[P], GARBAGE_DUMP_X, GARBAGE_DUMP_Y, 0)
			call DeleteBackgroundHero(P)
		endif

		set i = 1
		loop
			exitwhen i > NUMBER_OF_HEROES
			call SetButtonTextures(i, not HeroCanBePicked(Hero.list[i]))
			set i = i + 1
		endloop

		call SetButtonTextures(RANDOM_HERO, false)
		call SetButtonTextures(SUGGEST_RANDOM, false)
		call SetButtonTextures(PAGE_DOWN, false)
		call SetButtonTextures(PAGE_UP, false)

		if localPlayerId == P then
			call ClearSelection()
			call BlzFrameSetVisible(heroSelectionMenu, true)
			call BlzFrameSetVisible( heroSelectionButtonHighlight , false )
			static if HIDE_GAME_UI then
				call BlzHideOriginFrames(true)
				call BlzFrameSetAllPoints(BlzGetOriginFrame(ORIGIN_FRAME_WORLD_FRAME, 0), BlzGetOriginFrame(ORIGIN_FRAME_GAME_UI, 0))
				call BlzFrameSetVisible(consoleUIBackdrop, false)
			endif
		endif

		static if CREATE_SHADOWS then
			call RemoveDestructable(foregroundHeroShadow)
			if preselectedHeroIndex[localPlayerId] == 0 or playerHasHero[localPlayerId] then
				set id = NO_SHADOW_DESTRUCTABLE_ID
			else
				set id = SHADOW_DESTRUCTABLE_ID
			endif
			set foregroundHeroShadow = CreateDestructable(id, localForegroundHeroX, localForegroundHeroY, 0, 1, 0)
		endif

		static if ENFORCE_CAMERA then
			call TimerStart(lockCameraTimer, 0.01, true, function LockSelecterCamera)
		endif

		call onReturn.evaluate(whichPlayer)

		call NoCrash("PlayerReturnToHeroSelection")
	endfunction
	
	function BeginHeroSelection takes nothing returns nothing
		local integer i = 1
		local integer P
		local noArgCallback onBegin = HeroSelectionOnBegin

		if heroSelectionMenu == null then
			static if HERO_SELECTION_ENABLE_DEBUG_MODE then
				call BJDebugMsg("|cffff0000Warning:|r Attempted to begin hero selection, but hero selection was not initialized...")
			endif
			return
		endif

		call InitCrashCheck("BeginHeroSelection")

		static if HIDE_GAME_UI then
			call BlzHideOriginFrames(true)
			call BlzFrameSetAllPoints(BlzGetOriginFrame(ORIGIN_FRAME_WORLD_FRAME, 0), BlzGetOriginFrame(ORIGIN_FRAME_GAME_UI, 0))
			call BlzFrameSetVisible(BlzGetFrameByName("ConsoleUIBackdrop",0), false)
		endif

		loop
			exitwhen i > numSelectingPlayers
			set P = playerNumberOfSlot[i]
			set isInHeroSelection[P] = true
			set numPlayersInSelection = numPlayersInSelection + 1
			if localPlayerId == P then
				call ClearSelection()
			endif
			set i = i + 1
		endloop

		static if CREATE_SHADOWS then
			set foregroundHeroShadow = CreateDestructable(NO_SHADOW_DESTRUCTABLE_ID, localForegroundHeroX, localForegroundHeroY, 0, 1, 0)
		endif

		static if SHOW_MENU_BEFORE_ENABLED then
			call BlzFrameSetVisible(heroSelectionMenu, true)
		endif
		call BlzFrameSetVisible( heroSelectionButtonHighlight , false )

		static if ENFORCE_CAMERA then
			call TimerStart(lockCameraTimer, 0.01, true, function LockSelecterCamera)
		endif

		call onBegin.evaluate()

		call NoCrash("BeginHeroSelection")
	endfunction

	function RestartHeroSelection takes nothing returns nothing
		local integer i = 1
		local integer P
        local noArgCallback onRestart = HeroSelectionOnRestart

		if heroSelectionMenu == null then
			static if HERO_SELECTION_ENABLE_DEBUG_MODE then
				call BJDebugMsg("|cffff0000Warning:|r Attempted to restart hero selection, but hero selection was not initialized...")
			endif
			return
		endif

		if numPlayersInSelection > 0 then
			static if HERO_SELECTION_ENABLE_DEBUG_MODE then
				call BJDebugMsg("|cffff0000Warning:|r Attempted to restart hero selection while there are players in hero selection...")
			endif
			return
		endif

		call InitCrashCheck("RestartHeroSelection")
		
		call onRestart.evaluate()

		loop
			exitwhen i > numSelectingPlayers
			set P = playerNumberOfSlot[i]
			set playerHasHero[P] = false
			set playerHasBan[P] = false
			set isInHeroSelection[P] = false
			set heroSelectionDisabledForPlayer[P] = true
			set pickedHeroIndex[P] = 0

			static if CREATE_BACKGROUND_HEROES and not DELETE_BACKGROUND_HEROES_AFTER_END then
				call BlzSetSpecialEffectPosition(backgroundHero[P], GARBAGE_DUMP_X, GARBAGE_DUMP_Y, 0)
				call DeleteBackgroundHero(P)
			endif
			set i = i + 1
		endloop

		set i = 1
		loop
			exitwhen i > NUMBER_OF_HEROES
			set heroIndexWasPicked[i] = false
			set heroIndexWasBanned[i] = false
			set i = i + 1
		endloop

		set numPlayersWithHero = 0
		set numPlayersInSelection = 0

		call NoCrash("RestartHeroSelection")

		call BeginHeroSelection()
	endfunction

	function InitHeroSelection takes nothing returns nothing
		local integer i = 0
		local integer j
		local integer P
		local trigger leaveTrigger = CreateTrigger()
		local integer array teamSize
		local integer array slotInTeam
		local location tempLoc
		local real array cameraTargetPositionX
		local real array cameraTargetPositionY
		local real cameraEyePositionX
		local real cameraEyePositionY

		call InitCrashCheck("InitHeroSelection")

        call InitFullScreenParents()

		set GARBAGE_DUMP_X = GetRectMaxX(GetPlayableMapRect())
		set GARBAGE_DUMP_Y = GetRectMaxY(GetPlayableMapRect())

		set localPlayerId = GetPlayerId(GetLocalPlayer())
		call HeroSelectionInitPlayers()
        call HeroSelectionSetPlayerColors()

		//Player List
		set i = 0
		loop
			exitwhen i > 23
			static if not AUTO_SET_SELECTING_PLAYERS then
				if PLAYER_SELECTS_HERO[i] and GetPlayerSlotState(Player(i)) == PLAYER_SLOT_STATE_PLAYING then
					set numSelectingPlayers = numSelectingPlayers + 1
					set playerNumberOfSlot[numSelectingPlayers] = i
					set coloredPlayerName[i] = PLAYER_COLOR[i] + GetPlayerName(Player(i)) + "|r"
					set heroPreselectionDisabledForPlayer[i] = not PRE_SELECT_BEFORE_ENABLED
					set heroSelectionDisabledForPlayer[i] = true
					if GetPlayerController(Player(i)) == MAP_CONTROL_USER then
						set playerIsHuman[i] = true
						set numSelectingHumans = numSelectingHumans + 1
						call TriggerRegisterPlayerEvent(leaveTrigger, Player(i), EVENT_PLAYER_LEAVE)
					endif
				endif
			else
				if GetPlayerSlotState(Player(i)) == PLAYER_SLOT_STATE_PLAYING and GetPlayerController(Player(i)) == MAP_CONTROL_USER then
					set numSelectingPlayers = numSelectingPlayers + 1
					set playerNumberOfSlot[numSelectingPlayers] = i
					set coloredPlayerName[i] = PLAYER_COLOR[i] + GetPlayerName(Player(i)) + "|r"
					set heroPreselectionDisabledForPlayer[i] = not PRE_SELECT_BEFORE_ENABLED
					set heroSelectionDisabledForPlayer[i] = true
					set playerIsHuman[i] = true
					set numSelectingHumans = numSelectingHumans + 1
					call TriggerRegisterPlayerEvent(leaveTrigger, Player(i), EVENT_PLAYER_LEAVE)
				endif
			endif
			set i = i + 1
		endloop

		set NUMBER_OF_TEAMS = 0
		set i = 1
		loop
			exitwhen i > numSelectingPlayers
			set NUMBER_OF_TEAMS = IMaxBJ(NUMBER_OF_TEAMS, TEAM_OF_PLAYER[playerNumberOfSlot[i]])
			set i = i + 1
		endloop

		//Foreground hero locations are asynchronous. Background hero locations are not.
		static if SEPARATE_LOCATIONS_FOR_EACH_TEAM then
			call HeroSelectionInitArrays()
			set localForegroundHeroX = TEAM_FOREGROUND_HERO_X[TEAM_OF_PLAYER[localPlayerId]]
			set localForegroundHeroY = TEAM_FOREGROUND_HERO_Y[TEAM_OF_PLAYER[localPlayerId]]
			set localHeroSelectionAngle = TEAM_HERO_SELECTION_ANGLE[TEAM_OF_PLAYER[localPlayerId]]

			set i = 1
			loop
				exitwhen i > NUMBER_OF_TEAMS
				set cameraTargetPositionX[i] = TEAM_FOREGROUND_HERO_X[i] + Cos(Deg2Rad(TEAM_FOREGROUND_HERO_X[i]))*CAMERA_TARGET_OFFSET + Sin(Deg2Rad(TEAM_HERO_SELECTION_ANGLE[i]))*CAMERA_PERPENDICULAR_OFFSET
				set cameraTargetPositionY[i] = TEAM_FOREGROUND_HERO_Y[i] + Sin(Deg2Rad(TEAM_FOREGROUND_HERO_Y[i]))*CAMERA_TARGET_OFFSET - Cos(Deg2Rad(TEAM_HERO_SELECTION_ANGLE[i]))*CAMERA_PERPENDICULAR_OFFSET
				set i = i + 1
			endloop
		else
			set localForegroundHeroX = FOREGROUND_HERO_X
			set localForegroundHeroY = FOREGROUND_HERO_Y
			set localHeroSelectionAngle = HERO_SELECTION_ANGLE

			set i = 1
			loop
				exitwhen i > NUMBER_OF_TEAMS
				set cameraTargetPositionX[i] = FOREGROUND_HERO_X + Cos(Deg2Rad(localHeroSelectionAngle))*CAMERA_TARGET_OFFSET + Sin(Deg2Rad(HERO_SELECTION_ANGLE))*CAMERA_PERPENDICULAR_OFFSET
				set cameraTargetPositionY[i] = FOREGROUND_HERO_Y + Sin(Deg2Rad(localHeroSelectionAngle))*CAMERA_TARGET_OFFSET - Cos(Deg2Rad(HERO_SELECTION_ANGLE))*CAMERA_PERPENDICULAR_OFFSET
				set i = i + 1
			endloop
		endif

		call TriggerAddAction(leaveTrigger, function OnPlayerLeave)

		call SetCategories()
		call HeroDeclaration()

		set NUMBER_OF_HEROES = Hero.numHeroes
		set RANDOM_HERO = NUMBER_OF_HEROES + 1
		set SUGGEST_RANDOM = NUMBER_OF_HEROES + 2
		set PAGE_DOWN = NUMBER_OF_HEROES + 3
		set PAGE_UP = NUMBER_OF_HEROES + 4

		set i = 1
		loop
			exitwhen i > NUMBER_OF_HEROES
			set j = 0
			loop
				set j = j + 1
				exitwhen Hero.list[i].abilities[j] == 0
			endloop
			if j - 1 > NUMBER_OF_ABILITY_FRAMES then
				set NUMBER_OF_ABILITY_FRAMES = j - 1
			endif

			if Hero.list[i].category > NUMBER_OF_CATEGORIES then
				set NUMBER_OF_CATEGORIES = Hero.list[i].category
			endif

			call Hero.list[i].GetValues()

			set i = i + 1
		endloop

		set i = 1
		loop
			exitwhen i > NUMBER_OF_CATEGORIES
			set NUMBER_OF_PAGES = IMaxBJ(NUMBER_OF_PAGES, PAGE_OF_CATEGORY[i])
			set i = i + 1
		endloop

		//Camera
		call CameraSetupSetField(heroSelectionCamera , CAMERA_FIELD_ZOFFSET , CAMERA_Z_OFFSET , 0.0)
		call CameraSetupSetField(heroSelectionCamera , CAMERA_FIELD_ANGLE_OF_ATTACK , 360 - CAMERA_PITCH , 0.0)
		call CameraSetupSetField(heroSelectionCamera , CAMERA_FIELD_TARGET_DISTANCE , CAMERA_DISTANCE , 0.0)
		call CameraSetupSetField(heroSelectionCamera , CAMERA_FIELD_ROLL , 0.0 , 0.0)
		call CameraSetupSetField(heroSelectionCamera , CAMERA_FIELD_FIELD_OF_VIEW , CAMERA_FIELD_OF_VIEW , 0.0)
		call CameraSetupSetField(heroSelectionCamera , CAMERA_FIELD_FARZ , 7500.0 , 0.0)
		call CameraSetupSetField(heroSelectionCamera , CAMERA_FIELD_ROTATION , localHeroSelectionAngle , 0.0)
		call CameraSetupSetDestPosition(heroSelectionCamera , cameraTargetPositionX[TEAM_OF_PLAYER[localPlayerId]] , cameraTargetPositionY[TEAM_OF_PLAYER[localPlayerId]] , 0.0)

		//Background Heroes
		set i = 1
		loop
			exitwhen i > numSelectingPlayers
			set P = playerNumberOfSlot[i]
			set teamSize[TEAM_OF_PLAYER[P]] = teamSize[TEAM_OF_PLAYER[P]] + 1
			set slotInTeam[P] = teamSize[TEAM_OF_PLAYER[P]]
			set i = i + 1
		endloop

		set i = 1
		loop
			exitwhen i > numSelectingPlayers
			set P = playerNumberOfSlot[i]
			set tempLoc = GetPlayerBackgroundHeroLocation(P, i, numSelectingPlayers, TEAM_OF_PLAYER[P], slotInTeam[P], teamSize[TEAM_OF_PLAYER[P]])
			static if SEPARATE_LOCATIONS_FOR_EACH_TEAM then
				set cameraEyePositionX = cameraTargetPositionX[TEAM_OF_PLAYER[P]] - Cos(Deg2Rad(CAMERA_PITCH))*Cos(Deg2Rad(TEAM_HERO_SELECTION_ANGLE[TEAM_OF_PLAYER[P]]))*CAMERA_DISTANCE
				set cameraEyePositionY = cameraTargetPositionY[TEAM_OF_PLAYER[P]] - Cos(Deg2Rad(CAMERA_PITCH))*Sin(Deg2Rad(TEAM_HERO_SELECTION_ANGLE[TEAM_OF_PLAYER[P]]))*CAMERA_DISTANCE
				set BACKGROUND_HERO_X[P] = cameraEyePositionX - Sin(Deg2Rad(TEAM_HERO_SELECTION_ANGLE[TEAM_OF_PLAYER[P]]))*GetLocationX(tempLoc) + Cos(Deg2Rad(TEAM_HERO_SELECTION_ANGLE[TEAM_OF_PLAYER[P]]))*GetLocationY(tempLoc)
				set BACKGROUND_HERO_Y[P] = cameraEyePositionY + Cos(Deg2Rad(TEAM_HERO_SELECTION_ANGLE[TEAM_OF_PLAYER[P]]))*GetLocationX(tempLoc) + Sin(Deg2Rad(TEAM_HERO_SELECTION_ANGLE[TEAM_OF_PLAYER[P]]))*GetLocationY(tempLoc)
			else
				set cameraEyePositionX = cameraTargetPositionX[TEAM_OF_PLAYER[P]] - Cos(Deg2Rad(CAMERA_PITCH))*Cos(Deg2Rad(HERO_SELECTION_ANGLE))*CAMERA_DISTANCE
				set cameraEyePositionY = cameraTargetPositionY[TEAM_OF_PLAYER[P]] - Cos(Deg2Rad(CAMERA_PITCH))*Sin(Deg2Rad(HERO_SELECTION_ANGLE))*CAMERA_DISTANCE
				set BACKGROUND_HERO_X[P] = cameraEyePositionX - Sin(Deg2Rad(HERO_SELECTION_ANGLE))*GetLocationX(tempLoc) + Cos(Deg2Rad(HERO_SELECTION_ANGLE))*GetLocationY(tempLoc)
				set BACKGROUND_HERO_Y[P] = cameraEyePositionY + Cos(Deg2Rad(HERO_SELECTION_ANGLE))*GetLocationX(tempLoc) + Sin(Deg2Rad(HERO_SELECTION_ANGLE))*GetLocationY(tempLoc)
			endif
			call RemoveLocation(tempLoc)
			set i = i + 1
		endloop
		set tempLoc = null

		//Caption
		set captionFrame = BlzCreateFrameByType("TEXT", "captionFrame", BlzGetOriginFrame(ORIGIN_FRAME_WORLD_FRAME, 0), "", 0)
		call BlzFrameSetVisible(captionFrame , false)
		call BlzFrameSetEnable(captionFrame , false)
		call BlzFrameSetSize(captionFrame, 0.4, 0.05)
		call BlzFrameSetAbsPoint(captionFrame , FRAMEPOINT_CENTER , CAPTION_X , CAPTION_Y)
		call BlzFrameSetText(captionFrame, CAPTION_COLOR_1 + HERO_SELECTION_CAPTION + "|r")
		call BlzFrameSetTextAlignment(captionFrame , TEXT_JUSTIFY_MIDDLE , TEXT_JUSTIFY_CENTER)
		call BlzFrameSetScale(captionFrame, CAPTION_FONT_SIZE/10.)
		call BlzFrameSetAlpha(captionFrame , CAPTION_ALPHA_1)

		//Timer
		set timerFrame = BlzCreateFrameByType("TEXT", "timerFrame", BlzGetOriginFrame(ORIGIN_FRAME_WORLD_FRAME, 0), "", 0)
		call BlzFrameSetVisible(timerFrame , false)
		call BlzFrameSetEnable(timerFrame , false)
		call BlzFrameSetSize(timerFrame, 0.15, 0.05)
		call BlzFrameSetAbsPoint(timerFrame , FRAMEPOINT_CENTER , TIMER_X , TIMER_Y)
		call BlzFrameSetTextAlignment(timerFrame , TEXT_JUSTIFY_MIDDLE , TEXT_JUSTIFY_CENTER)
		call BlzFrameSetScale(timerFrame, TIMER_FONT_SIZE/10.)

		call NoCrash("InitHeroSelection")

		call InitMenu()
	endfunction
	
endlibrary


Showcase Map Credits:

Terrain by illbean.

Trees - Fingolfin
Night Elf Archway - eubz, Stormknight
Fog and Glow - Embercraft
Hero Highlight Effects - Vinz
Music - Nightsong (World of Warcraft)


Change Log

v1.3.2:
  • Fixed a bug that could cause a desync due hiding/showing ConsoleUIBackdrop locally.
  • Two additional arguments are passed to the HeroSelectionOnPick callback function. These are wasRandomPick and wasRepick.

v1.3:
  • Improved GUI documentation and import instructions.
  • Enabled hero selection menu and tooltips to be put into the widescreen area. For the menu, this is done by setting MENU_X_LEFT to a negative number. The menu will automatically be adjusted for players who aren't using widescreen monitors.
  • Added the option to add player name texttags over the background heroes. The option in the config is PLAYER_TEXT_TAGS under "Sounds and visuals of background heroes."

v1.2:
  • Added many page cycle button styles.
  • Added OnRestart callback function.
  • Moved all globals that can be used in callbacks into the callbacks block.
  • Cleaned up code and fixed minor bugs.

v1.1:
  • Added support for large number of heroes by adding option to setup multiple pages.
  • Split config and callbacks into two files.
  • You no longer need to set the model paths of heroes manually.
  • Tinting colors of heroes will now be shown.
  • Consolidated select and ban buttons into one button.
  • Added option to set the scale of the accept button.
  • Added option to lock top edge of tooltips instead of bottom edge.
  • Cleaned up code and fixed minor bugs.
Contents

Antares' Hero Selection v1.3.2 GUI (Map)

Antares' Hero Selection v1.3.2 Lua (Map)

Antares' Hero Selection v1.3.2 vJASS (Map)

Level 7
Joined
Sep 16, 2016
Messages
185
Wow this looks dope wtf, what if my map has over 120 characters and max 3 players in a team up to 3v3v3, do you think its possible to have it support all those playable characters?
 
Wow this looks dope wtf, what if my map has over 120 characters and max 3 players in a team up to 3v3v3, do you think its possible to have it support all those playable characters?
It will be difficult to get 120 heroes on the menu and still have any space for the heroes that were picked. You'd have to do it with different pages that you can cycle through, say 3 or 4 pages of 30 to 40 heroes. I don't have that implemented yet, but I'm planning on doing it. If you need it, I can prioritize it.

3v3v3 shouldn't be a problem. You'll need to use the full horizontal space though, so I would recommend putting the menu at the top, very wide with lots of columns but only few rows (it might look a bit janky, but I plan on improving that).
 
Level 7
Joined
Sep 16, 2016
Messages
185
It will be difficult to get 120 heroes on the menu and still have any space for the heroes that were picked. You'd have to do it with different pages that you can cycle through, say 3 or 4 pages of 30 to 40 heroes. I don't have that implemented yet, but I'm planning on doing it. If you need it, I can prioritize it.

3v3v3 shouldn't be a problem. You'll need to use the full horizontal space though, so I would recommend putting the menu at the top, very wide with lots of columns but only few rows (it might look a bit janky, but I plan on improving that).

I have a community for my map, and we have plans on improving the hero selection since its very outdated. Having categories that you can swap between like Agility, Strength, etc would be a huge thing, because our map has characters from over 10 different IPs, so to be able to categorize each IP with their own character would lessen the burden a lot. I feel bad making you prioritize this, since theres no rush to change our hero selection, it has always been a to-do thing, but not of utmost importance since we already have one in place, albeit many years old (xD)
 
Then when you get around to updating your hero selection, my system should support any number of heroes without a problem :psmile:. Just keep in mind that it will be a lot of hours importing all of your 120 heroes! :peek:

But it's probably a feature more people would like to see. A lot of people have far too many heroes in their map :plol:.
 
Level 7
Joined
Sep 16, 2016
Messages
185
Then when you get around to updating your hero selection, my system should support any number of heroes without a problem :psmile:. Just keep in mind that it will be a lot of hours importing all of your 120 heroes! :peek:

But it's probably a feature more people would like to see. A lot of people have far too many heroes in their map :plol:.
Hehe that sounds sweet Antares, yeah I guess I can't escape the work of importing all the heroes, but if its for a hero selection screen of your caliber, it is of no issue :grin:
 
Level 3
Joined
Feb 2, 2024
Messages
9
Does this work for older game versions like 1.26? For me it doesn't appear in the map list when I try to run it.
 
Does this work for older game versions like 1.26? For me it doesn't appear in the map list when I try to run it.
I helped someone set up one of my systems on 1.31 and it should be possible here too, but I'm afraid 1.26 is too far back.

@Regno multiple pages are now implemented. Still have to work on the layout options a bit more.
 
Level 7
Joined
Sep 16, 2016
Messages
185
I helped someone set up one of my systems on 1.31 and it should be possible here too, but I'm afraid 1.26 is too far back.

@Regno multiple pages are now implemented. Still have to work on the layout options a bit more.
Sweeeet! I'll give it a try right away. :)
 
Level 7
Joined
Sep 16, 2016
Messages
185
@Antares A I have some questions regarding the UI :grin:

By the way, I love the setup of the code so far, just wondering how feasible would a layout for the UI be for something like this with this system? A dropdown could also work to avoid category buttons but both would work nicely, I guess with a dropdown you save more space on the selection screen
1707817138891.png


With this, only the sound for the hero picked played for the player who picked it, so you don't hear the other unit sounds

This is one of the bugs with UI I noticed
1707817240879.png

:sad:
 
Level 7
Joined
Sep 16, 2016
Messages
185
Please attach your config/hero list so I can investigate what's going on.
JASS:
library HeroSelectionConfig requires optional NeatMessages

   //=================================================================================================================================
   //These constants determine important aspects of the hero selection. You can fine-tune visuals, sounds etc. further down below.
   //=================================================================================================================================

   globals
       constant boolean HERO_SELECTION_ENABLE_DEBUG_MODE    = true                    //Printout errors and check for function crashes.
 
       //Overall behavior.
       constant boolean HERO_CAN_BE_PICKED_MULTIPLE_TIMES    = false                 //Set to true if the same hero should be able to be picked multiple times.
       constant boolean AUTO_SET_SELECTING_PLAYERS            = false                    //Set to true if hero selection should be enabled for all human players. Set to false if you want to set the player list manually (which can include computer players).
       constant boolean COMPUTER_AUTO_PICK_RANDOM_HERO        = false                    //Set to true if computer players that are in hero selection should automatically pick a random hero after all human players have picked.
       constant boolean ESCAPE_PLAYER_AFTER_SELECTING        = false                 //Set to true if a player picking a hero should kick him or her out of the hero selection menu and camera.
       constant boolean CONCEAL_HERO_PICKS_FROM_ENEMIES    = false                 //Set to true if hero picks should be concealed from enemies (players in another team or all players if there are no teams), including emote, effect, and text messages.
       constant boolean MENU_INCLUDE_RANDOM_PICK            = true                    //Include button for picking a random hero at the bottom of the menu.
       constant boolean MENU_INCLUDE_SUGGEST_RANDOM        = true                    //Include button to pre-select a random hero next to the random pick button.
       constant real TIME_LIMIT                            = 60                    //Set a time limit after which players who haven't chosen a hero will be assigned one at random. Set to 0 for no time limit.

       //Camera and foreground hero positions (background hero locations are set in GetPlayerBackgroundHeroLocation).
       constant boolean SEPARATE_LOCATIONS_FOR_EACH_TEAM    = false                    //Set to true if each team gets a different selection screen (all following values will be ignored and you have to set the array equivalents in InitArrays).
       constant real FOREGROUND_HERO_X                        = 9421                    //x-Position of foreground hero.
       constant real FOREGROUND_HERO_Y                        = 9043                    //y-Position of foreground hero.
       constant real HERO_SELECTION_ANGLE                    = 0                        //Hero selection viewing direction of camera (0 = facing east, 90 = facing north etc.).

       //Visuals.
       constant boolean ENFORCE_CAMERA                        = true                    //Set to false if players' camera should not be changed during hero selection.
       constant boolean HIDE_GAME_UI                       = true                  //Set to true if all UI elements other than the hero selection menu should be hidden.
       constant boolean CREATE_FOREGROUND_HERO                = true                    //Set to false if no foreground hero should appear on preselection.
       constant boolean CREATE_BACKGROUND_HEROES              = true                  //Set to true if players should be able to see other players' picks in the background.
   endglobals

   //=================================================================================================================================

   globals
       string array CATEGORY_NAMES                            //Names of the hero categories that appear in the hero selection menu.
       integer array PAGE_OF_CATEGORY                        //Set the page at which that category appears within the menu. When there is more than one page, page cycle buttons appear in the menu.
       boolean array PLAYER_SELECTS_HERO                    //List of all players that are supposed to participate in hero selection (true = participates).
       integer array TEAM_OF_PLAYER                        //Team of player, 1, 2 etc. 0 = no teams.
       string array PLAYER_COLOR                            //Hex color string (including "|cff") for each player.
   endglobals

   function SetCategories takes nothing returns nothing
       //Set the names of the categories in the hero selection menu. Starts at index 1. Set to null for no category caption.
       set CATEGORY_NAMES[1]                        = "|cffffcc00Strength|r"
       set CATEGORY_NAMES[2]                        = "|cffffcc00Agility|r"
       set CATEGORY_NAMES[3]                        = "|cffffcc00Intelligence|r"
       set CATEGORY_NAMES[4]                        = "|cffffcc00Reborn|r"
       set CATEGORY_NAMES[5]                        = "|cffffcc00Bleach|r"

       //Set the pages of the categories in the hero selection menu. Pages start at 1.
       set PAGE_OF_CATEGORY[1]                        = 1
       set PAGE_OF_CATEGORY[2]                        = 2
       set PAGE_OF_CATEGORY[3]                        = 3
       set PAGE_OF_CATEGORY[4]                        = 4
       set PAGE_OF_CATEGORY[5]                        = 5
   endfunction

   function HeroSelectionInitPlayers takes nothing returns nothing
       //Set the list of players for which hero selection is enabled (only if AUTO_SET_SELECTING_PLAYERS = false). It is disabled here because computer players must be added to the hero selection.
       set PLAYER_SELECTS_HERO[0]                    = true
       set PLAYER_SELECTS_HERO[1]                    = true
       set PLAYER_SELECTS_HERO[2]                    = true
       set PLAYER_SELECTS_HERO[3]                    = false
       set PLAYER_SELECTS_HERO[4]                    = true
       set PLAYER_SELECTS_HERO[5]                    = true
       set PLAYER_SELECTS_HERO[6]                    = true
       set PLAYER_SELECTS_HERO[7]                    = false
       set PLAYER_SELECTS_HERO[8]                    = true
       set PLAYER_SELECTS_HERO[9]                    = true
       set PLAYER_SELECTS_HERO[10]                    = true
       set PLAYER_SELECTS_HERO[11]                    = false
 
       //Set the teams of players. Players with team 0 are enemies to all players.
       set TEAM_OF_PLAYER[0]                        = 1
       set TEAM_OF_PLAYER[1]                        = 1
       set TEAM_OF_PLAYER[2]                        = 1
       set TEAM_OF_PLAYER[3]                        = 1
       set TEAM_OF_PLAYER[4]                        = 2
       set TEAM_OF_PLAYER[5]                        = 2
       set TEAM_OF_PLAYER[6]                        = 2
       set TEAM_OF_PLAYER[7]                        = 2
       set TEAM_OF_PLAYER[8]                        = 3
       set TEAM_OF_PLAYER[9]                        = 3
       set TEAM_OF_PLAYER[10]                        = 3
       set TEAM_OF_PLAYER[11]                        = 3
   endfunction

   //=================================================================================================================================

   globals
       real array TEAM_FOREGROUND_HERO_X                    //x-Position of foreground hero.
       real array TEAM_FOREGROUND_HERO_Y                    //y-Position of foreground hero.
       real array TEAM_HERO_SELECTION_ANGLE                //Hero selection camera yaw in degrees (0 = facing east, 90 = facing north etc.).
   endglobals

   function HeroSelectionInitArrays takes nothing returns nothing
       //If you set SEPARATE_LOCATIONS_FOR_EACH_TEAM = true, initialize the above array variables here.
   endfunction
 
   function GetPlayerBackgroundHeroLocation takes integer playerIndex, integer whichSlot, integer numberOfPlayers, integer whichTeam, integer slotInTeam, integer teamSize returns location
       local real deltaHorizontal
       local real deltaVertical

       //=================================================================================================================================
       /*
        CREATE_BACKGROUND_HEROES only.
        Here you can customize the positions of the background heroes. Init loops through and calls this function for each player.

        Input:
        playerIndex
        whichSlot                 The position of the player when enumerating all players for which hero selection is enabled.
        numberOfPlayers         The number of players for which hero selection is enabled.
        whichTeam                 The team the player is assigned to. Discard if there are no teams.
        slotInTeam                 The position of that player within his or her team. Discard if there are no teams.
        teamSize                 The size of the player's team. Discard if there are no teams.

        Output:
        deltaVertical            The distance between the background hero and the camera eye position along the camera viewing direction.
        deltaHorizontal            The offset between the background hero and the camera eye position perpendicular to the camera viewing direction.
        */
       //==================================================================================================================================

       //Example code:
       local real angle
       local real dist
       local real BACKGROUND_HERO_OFFSET_BACKROW = 1425
       local real BACKGROUND_HERO_OFFSET_FRONTROW = 1250

       if teamSize == 1 then
           set angle = 9
           set dist = BACKGROUND_HERO_OFFSET_BACKROW
       elseif slotInTeam == 1 then
           set angle = 5
           set dist = BACKGROUND_HERO_OFFSET_BACKROW
       elseif slotInTeam == 2 then
           set angle = 13
           set dist = BACKGROUND_HERO_OFFSET_BACKROW
       else
           set angle = 9
           set dist = BACKGROUND_HERO_OFFSET_FRONTROW
       endif
 
       if whichTeam == 2 then
           set angle = -angle
       endif

       set deltaHorizontal = Sin(Deg2Rad(angle))*dist
       set deltaVertical = Cos(Deg2Rad(angle))*dist

       //==================================================================================================================================

       return Location(deltaHorizontal, deltaVertical)
   endfunction



   //==================================================================================================================================
   //                                                    F I N E - T U N I N G
   //==================================================================================================================================



   globals
       //How hero selection looks like before it is enabled (you can ignore this if you plan on enabling hero selection right away).
       constant boolean SHOW_MENU_BEFORE_ENABLED            = false                    //Set to true if players should be able to see the hero selection menu before hero selection is enabled.
       constant boolean PRE_SELECT_BEFORE_ENABLED          = false                 //Set to true if players should be able to pre-select heroes before hero selection is enabled.
 
       //Camera setup (ENFORCE_CAMERA).
       constant real CAMERA_PITCH                            = 12                    //Hero selection camera angle of attack in degrees. 0 = facing horizon, 90 = facing ground.
       constant real CAMERA_DISTANCE                        = 1300                    //Hero selection camera distance from camera target.
       constant real CAMERA_TARGET_OFFSET                    = 500                    //Distance between foreground hero and camera target. Positive = Camera target is behind the hero.
       constant real CAMERA_PERPENDICULAR_OFFSET            = 0                        //Shifts the camera left (negative) or right (positive) with respect to the foreground hero.
       constant real CAMERA_Z_OFFSET                        = 100                    //Hero selection camera z-offset.
       constant real CAMERA_FIELD_OF_VIEW                    = 70                    //Hero selection camera field of view.

       //Sounds and visuals of hero pre-selection (CREATE_FOREGROUND_HERO).
       constant string PRESELECT_EFFECT                    = null                    //Special effect played on the hero's position for the triggering player when switching to a new hero during pre-selection. Set to null for no effect.
       constant boolean PLAY_EMOTE_ON_PRESELECT            = true                  //Set to true if the hero should play its selection emote when the player switches to that hero during pre-selection.
       constant boolean PLAY_ANIMATION_ON_PRESELECT        = true                  //Set to true if the hero should play the selection animation when the player switches to that hero during pre-selection.
       constant boolean PHANTOM_HERO_WHEN_CANNOT_BE_PICKED    = true                    //Set to true if a hero should be black and transparent when it is pre-selected but cannot be picked.
       constant real FOREGROUND_HERO_Z                        = 0                        //z-Position of foreground hero.

       //Sounds and visuals on hero pick.
       constant string PICK_EFFECT                            = "HolyLight.mdx"        //Special effect played on the hero's position for the triggering player when picking a hero. Set to null for no effect.
       constant string PICK_SOUND                            = "Sound\\Interface\\ItemReceived.flac"    //Sound effect played for the triggering player when selecting a hero. Set to null for no sound.
       constant boolean PLAY_EMOTE_ON_PICK                    = false                 //Set to true if a hero should play its selection emote when a player chooses that hero.
       constant boolean PLAY_ANIMATION_ON_PICK                = false                 //Set to true if a hero should play the selection animation when a player chooses that hero.
       constant real PLAYER_PICK_ESCAPE_DELAY                = 4.0                    //Delay between selecting a hero and being kicked out of hero selection (ESCAPE_PLAY_AFTER_SELECTING only).
       constant real FOREGROUND_HERO_FADEOUT_DELAY            = 1.0                   //The time it takes for the foreground hero to start fading out after being selected.
       constant real FOREGROUND_HERO_FADEOUT_TIME            = 1.5                   //The time it takes for the foreground hero to fade out after being selected.
       constant string OTHER_PLAYER_HERO_PICK_SOUND        = "Sound\\Interface\\InGameChatWhat1.flac"    //Sound played when another player picks a hero. Set to null for no sound.
 
       //Text messages on hero pick.
       constant boolean CREATE_TEXT_MESSAGE_ON_PICK        = true                    //Set to true if a text message should be sent to all other players when a hero is picked (except to enemies when concealed).
       constant real TEXT_MESSAGE_X_OFFSET                    = 0.35                    //x-Offset of text messages from default. Text messages will still suck. Recommend using NeatMessages.
       constant real TEXT_MESSAGE_Y_OFFSET                    = 0                        //y-Offset of text messages from default.
       constant boolean USE_HERO_PROPER_NAME                = false                    //Instead of the hero's name, its proper name will be displayed in the text message. For example, "Uther" instead of "Paladin". Will temporarily create a hero to get the proper name.
       constant boolean MESSAGE_EVEN_WHEN_CONCEALED        = true                    //Set to true if players should still get a message notifying that a player has picked a hero even when it is concealed which hero was picked (CONCEAL_HERO_PICKS_FROM_ENEMIES).
       constant boolean INCLUDE_PROGRESSION_IN_MESSAGE        = false                 //Set to true if the displayed text message should include how many players have selected their hero.

       //Sounds and visuals of background heroes (CREATE_BACKGROUND_HEROES).
       constant real BACKGROUND_HERO_FADEIN_TIME            = 1.0                   //The time it takes for the background hero to fade in after being selected.
       constant boolean PLAY_EMOTE_ON_BACKGROUND_HERO      = true                     //Set to true if a hero should play its selection emote for all other players as it fades in.
       constant boolean PLAY_ANIMATION_ON_BACKGROUND_HERO  = true                  //Set to true if the background hero should play its selection animation as it fades in.
       constant string BACKGROUND_HERO_FADEIN_EFFECT       = "HolyLightRoyal.mdx"  //Special effect played on the background hero as it fades in.
       constant string BACKGROUND_HERO_SELF_HIGHLIGHT        = "RadianceHoly.mdx"    //Special effect added at the location of a player's own background hero to highlight it. Set to null for no highlight.
       constant real BACKGROUND_HERO_HIGHLIGHT_Z            = 70                    //z-Position of background hero self highlight.
       constant real BACKGROUND_HERO_FACING_POINT_OFFSET    = -500                    //Adjusts where the background heroes face. 0 = Background heroes will face the foreground hero. Negative value = Face a point closer to the camera. Positive value = Face a point further from the camera.
       constant string CONCEALED_HERO_EFFECT                = "Objects\\InventoryItems\\QuestionMark\\QuestionMark.mdl"    //Model for the background hero seen by a player for which the hero pick was concealed.

       //Last player picks a hero.
       constant real LAST_PLAYER_SELECT_END_DELAY            = 6.0                    //The amount of time after the last player selects a hero and the hero selection ends (ignore if ESCAPE_PLAYER_AFTER_SELECTING)
       constant boolean DELETE_BACKGROUND_HEROES_AFTER_END    = false                    //Set to false if background heroes should not get removed when hero selection ends. For example, when a player repicks, they are still there.

       //Layout of hero selection menu.
       constant integer MENU_NUMBER_OF_COLUMNS                = 2                        //Number of hero buttons per row.
       constant real MENU_X_LEFT                            = 0                        //x-Position of left edge of hero selection menu.
       constant real MENU_Y_TOP                            = 0.55                    //y-Position of top edge of hero selection menu.
       constant real MENU_BUTTON_SIZE                        = 0.039                    //Size of individual hero buttons.
       constant real MENU_LEFT_RIGHT_EDGE_GAP                = 0.02                    //Gap between left and right edges of menu and first and last buttons.
       constant real MENU_TOP_EDGE_GAP                        = 0.015                    //Gap between top edge of menu and first button/first category title.
       constant real MENU_BOTTOM_EDGE_GAP                    = 0.02                    //Gap between bottom edge of menu and last button.
       constant real MENU_BUTTON_BUTTON_GAP                = 0.005                    //Gap between two individual hero buttons.
       constant real MENU_CATEGORY_FONT_SIZE                = 16                    //Font size of category titles.
       constant real MENU_CATEGORY_GAP                        = 0.028                    //Gap between buttons of two different categories.
       constant real MENU_CATEGORY_TITLE_Y                    = -0.001                //y-Position shift between category title and center of gap between categories.
       constant real MENU_BORDER_TILE_SIZE                    = 0.03                    //This rounds up the width and height of the menu to an integer multiple of the specified number. This is useful if you're using a tiled border texture, so that there's no discontinuity in the texture.
                                                                                   //Value should be equal to BackdropCornerSize in the HeroSelectionMenu.fdf. Set to 0 to disable.
       constant real MENU_HEROES_RANDOM_GAP                = 0.02                    //Additional gap between last hero button and random pick button.
 
       //Select button.
       constant string SELECT_BUTTON_TEXT                    = "Accept"                //The text in the select hero button at the bottom of the menu.
       constant real SELECT_BUTTON_SCALE                    = 1.0                    //The scale of the select hero button at the bottom of the menu.
       constant real SELECT_BUTTON_WIDTH                    = 0.092                    //The width of the select hero button at the bottom of the menu.

       //Display of random options.
       constant string RANDOM_HERO_TOOLTIP                    = "Choose a random hero."
       constant string SUGGEST_RANDOM_TOOLTIP                = "Suggest a random hero, but don't select it just yet."
       constant string RANDOM_HERO_ICON                    = "ReplaceableTextures\\CommandButtons\\BTNRandomIncredibleIcon.blp"
       constant string SUGGEST_RANDOM_ICON                    = "ReplaceableTextures\\CommandButtons\\BTNSelectHeroOn.blp"
       constant boolean RANDOM_SELECT_CYCLE_STYLE            = true                    //Set to true if the foreground hero should cycle randomly between different heroes while random hero is pre-selected. Set to false if a question mark should be shown.
       constant real RANDOM_SELECT_CYCLE_INTERVAL            = 0.2                    //How fast the heroes are cycled through when random hero is pre-selected (RANDOM_SELECT_CYCLE_STYLE).
 
       //Layout of ability preview buttons.
       constant real HERO_ABILITY_PREVIEW_BUTTON_X            = 0.0                    //x-Position of topmost ability preview button relative to topright corner of menu.
       constant real HERO_ABILITY_PREVIEW_BUTTON_Y            = -0.016                //y-Position of topmost ability preview button relative to topright corner of menu.
       constant real HERO_ABILITY_PREVIEW_BUTTON_SIZE        = 0.025                    //The size of the hero ability preview buttons that appear on the right side of the menu when pre-selecting a hero.
       constant boolean ABILITY_BUTTON_HORIZONTAL_LAYOUT    = false                    //Set to true if ability preview buttons should be arranged horizontally instead of vertically.
       constant string HERO_ABILITY_LEVEL_DELIMITER        = " - ["                //To get the name of a hero ability without the level-text from the tooltip (such as "Stormbolt - [Level 1]" -> "Stormbolt"). This string is used to detect where the name of the ability ends. Set to null if not necessary.

       //Page cycle buttons (when multiple pages are set).
       constant real MENU_PAGE_DOWN_X                        = 0.03                    //x-Position of the page cycle down button relative to bottomleft corner of menu.                  
       constant real MENU_PAGE_DOWN_Y                        = 0.03                    //y-Position of the page cycle down button relative to bottomleft corner of menu.
       constant real MENU_PAGE_UP_X                        = 0.03                    //x-Position of the page cycle up button relative to bottomright corner of menu.
       constant real MENU_PAGE_UP_Y                        = 0.03                    //y-Position of the page cycle up button relative to bottomright corner of menu.
       constant string MENU_PAGE_DOWN_ICON                    = "ReplaceableTextures\\CommandButtons\\BTNNagaBurrow.blp"
       constant string MENU_PAGE_UP_ICON                    = "ReplaceableTextures\\CommandButtons\\BTNNagaUnBurrow.blp"

       //Display of big caption.
       constant string HERO_SELECTION_CAPTION                = "Choose your Hero!"    //The text displayed on the screen during hero selection. Set null to omit text.
       constant real CAPTION_FONT_SIZE                        = 30                    //Font size of hero selection caption.
       constant real CAPTION_X                                = 0.4                    //x-Position of caption center.
       constant real CAPTION_Y                                = 0.41                    //y-Position of caption center.
       constant string CAPTION_COLOR_1                        = "|cffffcc00"            //Caption will cycle between color 1 and color 2. Set the same for no color cycling.
       constant string CAPTION_COLOR_2                        = "|cffffffff"            //Caption will cycle between color 1 and color 2. Set the same for no color cycling.
       constant integer CAPTION_ALPHA_1                    = 255                    //Caption will cycle between alpha 1 and alpha 2. Set the same for no alpha cycling.
       constant integer CAPTION_ALPHA_2                    = 255                    //Caption will cycle between alpha 1 and alpha 2. Set the same for no alpha cycling.
       constant real CAPTION_CYCLE_TIME                    = 4.0                    //The time it takes for the caption to cycle between color 1/2 and alpha 1/2.
       constant real CAPTION_FADEOUT_TIME                    = 2.0                    //The time it takes for the caption to fade out after a player has picked a hero. Set to -1 for no fade out.
 
       //Position of hero button and ability preview button mouse-over tooltips.
       constant real TOOLTIP_LEFT_X                        = 0.51
       constant real TOOLTIP_Y                              = 0.13
       constant boolean TOOLTIP_LOCK_TOP                    = false                    //Set to true if TOOLTIP_Y should refer to top instead of bottom edge.
       constant real TOOLTIP_WIDTH                         = 0.29

       //Display of countdown timer.
       constant string TIMER_TEXT                            = "Time Remaining: |cffffcc00"    //Text before the time remaining display
       constant string TIMER_BAN_PHASE_TEXT                = "Ban Phase: |cffffcc00"        //Text before the time remaining display during the ban phase.
       constant real TIMER_FONT_SIZE                        = 11                    //Font size of the time remaining display.
       constant real TIMER_X                                = 0.4                    //x-Position of center of the time remaining display.
       constant real TIMER_Y                                = 0.59                    //y-Position of the center of the time remaining display.

       //Shadows of heroes.
       constant boolean CREATE_SHADOWS                        = false                    //Create shadows with destructables for heroes (since they are special effects and don't have shadows).
       constant integer SHADOW_DESTRUCTABLE_ID                = 'Dsha'                //Destructable id of the shadow that's created for heroes.
       constant integer NO_SHADOW_DESTRUCTABLE_ID            = 'Dnsh'                //Dummy destructable without a shadow.
   endglobals

   //=================================================================================================================================

   function HeroSelectionSetPlayerColors takes nothing returns nothing
       //Set the colors of players shown for their names in text messages.
       set PLAYER_COLOR[0]                         = "|cffff0402"
       set PLAYER_COLOR[1]                         = "|cff1052ff"
       set PLAYER_COLOR[2]                         = "|cff1BE6BA"
       set PLAYER_COLOR[3]                         = "|cff8530b1"
       set PLAYER_COLOR[4]                         = "|cfffffc00"
       set PLAYER_COLOR[5]                         = "|cffff8a0d"
       set PLAYER_COLOR[6]                         = "|cff20bf00"
       set PLAYER_COLOR[7]                            = "|cffE35BAF"
       set PLAYER_COLOR[8]                            = "|cff949697"
       set PLAYER_COLOR[9]                         = "|cff7EBFF1"
       set PLAYER_COLOR[10]                         = "|cff106247"
       set PLAYER_COLOR[11]                         = "|cff4F2B05"
       set PLAYER_COLOR[12]                         = "|cff9C0000"
       set PLAYER_COLOR[13]                         = "|cff0000C2"
       set PLAYER_COLOR[14]                         = "|cff00EBEB"
       set PLAYER_COLOR[15]                        = "|cffBE00FF"
       set PLAYER_COLOR[16]                        = "|cffECCC86"
       set PLAYER_COLOR[17]                         = "|cffF7A48B"
       set PLAYER_COLOR[18]                         = "|cffBFFF80"
       set PLAYER_COLOR[19]                         = "|cffDBB8EC"
       set PLAYER_COLOR[20]                         = "|cff4F4F55"
       set PLAYER_COLOR[21]                         = "|cffECF0FF"
       set PLAYER_COLOR[22]                         = "|cff00781E"
       set PLAYER_COLOR[23]                        = "|cffA46F34"
   endfunction

endlibrary

JASS:
library HeroDeclaration
    globals
        Hero MountainKing
        Hero Paladin
        Hero Archmage
        Hero BloodMage
        Hero PriestessOfTheMoon
        Hero DemonHunter
        Hero KeeperOfTheGrove
        Hero Warden
        Hero Warden1
        Hero Warden2
        Hero Warden3
        Hero Warden4
        Hero Warden5
        Hero Warden6
        Hero Warden7
    endglobals
 
    function HeroDeclaration takes nothing returns nothing
        //========================================================================================
        //Here you declare all heroes in your map. The order in which you create them determine the order in which they appear in the hero selection menu.
        //The fields that should be set are:
        /*
        integer array abilities                     The abilities of this hero. Starts at index 1.
        boolean array isNonHeroAbility              Set this flag if the ability at the index is a non-hero ability. This will make the tooltip instead of the research tooltip appear on mouse-over.
        integer unitId                              Unit id of the hero.
        integer tooltipAbility                      Set up any non-hero ability with one level for each hero and write the hero's description in its tooltip.
        string selectEmote                          Sound path of the emote the hero should play when selected.
        animtype selectAnim                         animType of select animation. For example, "spell" is ANIMTYPE_SPELL.
        subanimtype selectSubAnim                   subanimtype of select animation. For example "spell slam" is ANIMTYPE_SPELL + SUBANIMTYPE_SLAM.
        real selectAnimLength                       Length of the select animation. If set incorrectly, animation will be interrupted or freeze.
        integer category                            In which category in the menu should this hero be put?
        boolean needsHeroGlow                       A hero glow will be added to heroes with models that don't have a glow. Requires "GeneralHeroGlow.mdx" to be imported.
        boolean unavailable                         Heroes with this flag will appear in the menu but cannot be picked.
        boolean array unavailableToTeam             Hero will not appear in the menu for players in a team for which this flag was set. Can be used to create completely different hero rosters for different teams.
        */
        //========================================================================================
        set MountainKing                            = Hero.create()
        set Paladin                                 = Hero.create()
        set Archmage                                = Hero.create()
        set BloodMage                               = Hero.create()
        set PriestessOfTheMoon                      = Hero.create()
        set DemonHunter                             = Hero.create()
        set KeeperOfTheGrove                        = Hero.create()
        set Warden                                  = Hero.create()
        set Warden1                                 = Hero.create()
        set Warden2                                 = Hero.create()
        set Warden3                                 = Hero.create()
        set Warden4                                 = Hero.create()
        set Warden5                                 = Hero.create()
        set Warden6                                 = Hero.create()
        set Warden7                                 = Hero.create()
        //========================================================================================
        set Archmage.abilities[1]                   = 'AHbz'
        set Archmage.abilities[2]                   = 'AHwe'
        set Archmage.abilities[3]                   = 'AHab'
        set Archmage.selectEmote                    = "Units\\Human\\HeroArchMage\\HeroArchMageWarcry1.flac"
        set Archmage.unitId                         = 'Hamg'
        set Archmage.tooltipAbility                 = 'T000'
        set Archmage.needsHeroGlow                  = false
        set Archmage.category                       = 3
        set Archmage.selectAnim                     = ANIM_TYPE_SPELL
        set Archmage.selectSubAnim                  = null
        set Archmage.selectAnimLength               = 2.7
        //========================================================================================
   
        set Paladin.abilities[1]                    = 'AHhb'
        set Paladin.abilities[2]                    = 'AHds'
        set Paladin.abilities[3]                    = 'AHad'
        set Paladin.abilities[4]                    = 'AHre'
        set Paladin.selectEmote                     = "Units\\Human\\HeroPaladin\\HeroPaladinYesAttack1.flac"
        set Paladin.unitId                          = 'Hpal'
        set Paladin.tooltipAbility                  = 'Tpal'
        set Paladin.needsHeroGlow                   = false
        set Paladin.category                        = 1
        set Paladin.selectAnim                      = ANIM_TYPE_SPELL
        set Paladin.selectSubAnim                   = null
        set Paladin.selectAnimLength                = 2.167
   
        //========================================================================================
   
        set MountainKing.abilities[1]               = 'AHtb'
        set MountainKing.abilities[2]               = 'AHtc'
        set MountainKing.abilities[3]               = 'AHbh'
        set MountainKing.abilities[4]               = 'AHav'
        set MountainKing.selectEmote                = "Units\\Human\\HeroMountainKing\\HeroMountainKingYesAttack1.flac"
        set MountainKing.unitId                     = 'Hmkg'
        set MountainKing.tooltipAbility             = 'Tmkg'
        set MountainKing.needsHeroGlow              = false
        set MountainKing.category                   = 1
        set MountainKing.selectAnim                 = ANIM_TYPE_SPELL
        set MountainKing.selectSubAnim              = SUBANIM_TYPE_SLAM
        set MountainKing.selectAnimLength           = 1.0
   
        //========================================================================================
   
        set BloodMage.abilities[1]                  = 'AHfs'
        set BloodMage.abilities[2]                  = 'AHbn'
        set BloodMage.abilities[3]                  = 'AHdr'
        set BloodMage.abilities[4]                  = 'AHpx'
        set BloodMage.selectEmote                   = "Units\\Human\\HeroBloodElf\\BloodElfMageYesAttack2.flac"
        set BloodMage.unitId                        = 'Hblm'
        set BloodMage.tooltipAbility                = 'Tblm'
        set BloodMage.needsHeroGlow                 = false
        set BloodMage.category                      = 3
        set BloodMage.selectAnim                    = ANIM_TYPE_SPELL
        set BloodMage.selectSubAnim                 = SUBANIM_TYPE_CHANNEL
        set BloodMage.selectAnimLength              = 2.0
   
        //========================================================================================
   
        set PriestessOfTheMoon.abilities[1]         = 'AEst'
        set PriestessOfTheMoon.abilities[2]         = 'AHfa'
        set PriestessOfTheMoon.abilities[3]         = 'AEar'
        set PriestessOfTheMoon.abilities[4]         = 'AEsf'
   
        set PriestessOfTheMoon.selectEmote          = "Units\\nightElf\\HeroMoonPriestess\\HeroMoonPriestessYesAttack2.flac"
        set PriestessOfTheMoon.unitId               = 'Emoo'
        set PriestessOfTheMoon.tooltipAbility       = 'Tmoo'
        set PriestessOfTheMoon.needsHeroGlow        = false
        set PriestessOfTheMoon.category             = 2
        set PriestessOfTheMoon.selectAnim           = ANIM_TYPE_SPELL
        set PriestessOfTheMoon.selectSubAnim        = null
        set PriestessOfTheMoon.selectAnimLength     = 1.333
        //========================================================================================
   
        set DemonHunter.abilities[1]                = 'AEmb'
        set DemonHunter.abilities[2]                = 'AEim'
        set DemonHunter.abilities[3]                = 'AEev'
        set DemonHunter.abilities[4]                = 'AEme'
   
        set DemonHunter.selectEmote                 = "Units\\NightElf\\HeroDemonHunter\\HeroDemonHunterYesAttack1.flac"
        set DemonHunter.unitId                      = 'Edem'
        set DemonHunter.tooltipAbility              = 'Tdem'
        set DemonHunter.needsHeroGlow               = false
        set DemonHunter.category                    = 2
        set DemonHunter.selectAnim                  = ANIM_TYPE_SPELL
        set DemonHunter.selectSubAnim               = SUBANIM_TYPE_THROW
        set DemonHunter.selectAnimLength            = 0.9
        //========================================================================================
   
        set KeeperOfTheGrove.abilities[1]           = 'AEer'
        set KeeperOfTheGrove.abilities[2]           = 'AEfn'
        set KeeperOfTheGrove.abilities[3]           = 'AEah'
        set KeeperOfTheGrove.abilities[4]           = 'AEtq'
   
        set KeeperOfTheGrove.selectEmote            = "Units\\NightElf\\HeroKeeperOfTheGrove\\KeeperOfTheGroveYesAttack2.flac"
        set KeeperOfTheGrove.unitId                 = 'Ekee'
        set KeeperOfTheGrove.tooltipAbility         = 'Tkee'
        set KeeperOfTheGrove.needsHeroGlow          = false
        set KeeperOfTheGrove.category               = 3
        set KeeperOfTheGrove.selectAnim             = ANIM_TYPE_STAND
        set KeeperOfTheGrove.selectSubAnim          = SUBANIM_TYPE_VICTORY
        set KeeperOfTheGrove.selectAnimLength       = 3.333
        //========================================================================================
   
        set Warden.abilities[1]                     = 'AEfk'
        set Warden.abilities[2]                     = 'AEbl'
        set Warden.abilities[3]                     = 'AEsh'
        set Warden.abilities[4]                     = 'AEsv'
   
        set Warden.selectEmote                      = "Units\\nightElf\\HeroWarden\\HeroWardenYesAttack3.flac"
        set Warden.unitId                           = 'Ewar'
        set Warden.tooltipAbility                   = 'Twar'
        set Warden.needsHeroGlow                    = false
        set Warden.category                         = 2
        set Warden.selectAnim                       = ANIM_TYPE_SPELL
        set Warden.selectSubAnim                    = null
        set Warden.selectAnimLength                 = 1.2
   
        //========================================================================================
   
        set Warden1.abilities[1]                    = 'AEfk'
        set Warden1.abilities[2]                    = 'AEbl'
        set Warden1.abilities[3]                    = 'AEsh'
        set Warden1.abilities[4]                    = 'AEsv'
   
        set Warden1.selectEmote                     = "Units\\nightElf\\HeroWarden\\HeroWardenYesAttack3.flac"
        set Warden1.unitId                          = 'Ewar'
        set Warden1.tooltipAbility                  = 'Twar'
        set Warden1.needsHeroGlow                   = false
        set Warden1.category                            = 2
        set Warden1.selectAnim                      = ANIM_TYPE_SPELL
        set Warden1.selectSubAnim                   = null
        set Warden1.selectAnimLength                    = 1.2
   
        //========================================================================================
   
        set Warden2.abilities[1]                    = 'AEfk'
        set Warden2.abilities[2]                    = 'AEbl'
        set Warden2.abilities[3]                    = 'AEsh'
        set Warden2.abilities[4]                    = 'AEsv'
   
        set Warden2.selectEmote                     = "Units\\nightElf\\HeroWarden\\HeroWardenYesAttack3.flac"
        set Warden2.unitId                          = 'Ewar'
        set Warden2.tooltipAbility                  = 'Twar'
        set Warden2.needsHeroGlow                   = false
        set Warden2.category                            = 2
        set Warden2.selectAnim                      = ANIM_TYPE_SPELL
        set Warden2.selectSubAnim                   = null
        set Warden2.selectAnimLength                    = 1.2
   
        //========================================================================================
   
        set Warden3.abilities[1]                    = 'AEfk'
        set Warden3.abilities[2]                    = 'AEbl'
        set Warden3.abilities[3]                    = 'AEsh'
        set Warden3.abilities[4]                    = 'AEsv'
   
        set Warden3.selectEmote                     = "Units\\nightElf\\HeroWarden\\HeroWardenYesAttack3.flac"
        set Warden3.unitId                          = 'Ewar'
        set Warden3.tooltipAbility                  = 'Twar'
        set Warden3.needsHeroGlow                   = false
        set Warden3.category                            = 2
        set Warden3.selectAnim                      = ANIM_TYPE_SPELL
        set Warden3.selectSubAnim                   = null
        set Warden3.selectAnimLength                    = 1.2
   
        //========================================================================================
   
        set Warden4.abilities[1]                    = 'AEfk'
        set Warden4.abilities[2]                    = 'AEbl'
        set Warden4.abilities[3]                    = 'AEsh'
        set Warden4.abilities[4]                    = 'AEsv'
   
        set Warden4.selectEmote                     = "Units\\nightElf\\HeroWarden\\HeroWardenYesAttack3.flac"
        set Warden4.unitId                          = 'Ewar'
        set Warden4.tooltipAbility                  = 'Twar'
        set Warden4.needsHeroGlow                   = false
        set Warden4.category                            = 2
        set Warden4.selectAnim                      = ANIM_TYPE_SPELL
        set Warden4.selectSubAnim                   = null
        set Warden4.selectAnimLength                    = 1.2
   
        //========================================================================================
   
        set Warden5.abilities[1]                    = 'AEfk'
        set Warden5.abilities[2]                    = 'AEbl'
        set Warden5.abilities[3]                    = 'AEsh'
        set Warden5.abilities[4]                    = 'AEsv'
   
        set Warden5.selectEmote                     = "Units\\nightElf\\HeroWarden\\HeroWardenYesAttack3.flac"
        set Warden5.unitId                          = 'Ewar'
        set Warden5.tooltipAbility                  = 'Twar'
        set Warden5.needsHeroGlow                   = false
        set Warden5.category                            = 2
        set Warden5.selectAnim                      = ANIM_TYPE_SPELL
        set Warden5.selectSubAnim                   = null
        set Warden5.selectAnimLength                    = 1.2
   
        //========================================================================================
   
        set Warden6.abilities[1]                    = 'AEfk'
        set Warden6.abilities[2]                    = 'AEbl'
        set Warden6.abilities[3]                    = 'AEsh'
        set Warden6.abilities[4]                    = 'AEsv'
   
        set Warden6.selectEmote                     = "Units\\nightElf\\HeroWarden\\HeroWardenYesAttack3.flac"
        set Warden6.unitId                          = 'Ewar'
        set Warden6.tooltipAbility                  = 'Twar'
        set Warden6.needsHeroGlow                   = false
        set Warden6.category                            = 2
        set Warden6.selectAnim                      = ANIM_TYPE_SPELL
        set Warden6.selectSubAnim                   = null
        set Warden6.selectAnimLength                    = 1.2
   
        //========================================================================================
   
        set Warden7.abilities[1]                    = 'AEfk'
        set Warden7.abilities[2]                    = 'AEbl'
        set Warden7.abilities[3]                    = 'AEsh'
        set Warden7.abilities[4]                    = 'AEsv'
   
        set Warden7.selectEmote                     = "Units\\nightElf\\HeroWarden\\HeroWardenYesAttack3.flac"
        set Warden7.unitId                          = 'Ewar'
        set Warden7.tooltipAbility                  = 'Twar'
        set Warden7.needsHeroGlow                   = false
        set Warden7.category                            = 2
        set Warden7.selectAnim                      = ANIM_TYPE_SPELL
        set Warden7.selectSubAnim                   = null
        set Warden7.selectAnimLength                    = 1.2
   
        //========================================================================================
    endfunction
endlibrary

JASS:
library HeroSelectionConfig requires optional NeatMessages
    //=================================================================================================================================
    //These constants determine important aspects of the hero selection. You can fine-tune visuals, sounds etc. further down below.
    //=================================================================================================================================
    globals
        constant boolean HERO_SELECTION_ENABLE_DEBUG_MODE   = true                  //Printout errors and check for function crashes.
 
        //Overall behavior.
        constant boolean HERO_CAN_BE_PICKED_MULTIPLE_TIMES  = false                 //Set to true if the same hero should be able to be picked multiple times.
        constant boolean AUTO_SET_SELECTING_PLAYERS         = false                 //Set to true if hero selection should be enabled for all human players. Set to false if you want to set the player list manually (which can include computer players).
        constant boolean COMPUTER_AUTO_PICK_RANDOM_HERO     = false                 //Set to true if computer players that are in hero selection should automatically pick a random hero after all human players have picked.
        constant boolean ESCAPE_PLAYER_AFTER_SELECTING      = false                 //Set to true if a player picking a hero should kick him or her out of the hero selection menu and camera.
        constant boolean CONCEAL_HERO_PICKS_FROM_ENEMIES    = false                 //Set to true if hero picks should be concealed from enemies (players in another team or all players if there are no teams), including emote, effect, and text messages.
        constant boolean MENU_INCLUDE_RANDOM_PICK           = true                  //Include button for picking a random hero at the bottom of the menu.
        constant boolean MENU_INCLUDE_SUGGEST_RANDOM        = true                  //Include button to pre-select a random hero next to the random pick button.
        constant real TIME_LIMIT                            = 60                    //Set a time limit after which players who haven't chosen a hero will be assigned one at random. Set to 0 for no time limit.
        //Camera and foreground hero positions (background hero locations are set in GetPlayerBackgroundHeroLocation).
        constant boolean SEPARATE_LOCATIONS_FOR_EACH_TEAM   = false                 //Set to true if each team gets a different selection screen (all following values will be ignored and you have to set the array equivalents in InitArrays).
        constant real FOREGROUND_HERO_X                     = 9421                  //x-Position of foreground hero.
        constant real FOREGROUND_HERO_Y                     = 9043                  //y-Position of foreground hero.
        constant real HERO_SELECTION_ANGLE                  = 0                     //Hero selection viewing direction of camera (0 = facing east, 90 = facing north etc.).
        //Visuals.
        constant boolean ENFORCE_CAMERA                     = true                  //Set to false if players' camera should not be changed during hero selection.
        constant boolean HIDE_GAME_UI                       = true                  //Set to true if all UI elements other than the hero selection menu should be hidden.
        constant boolean CREATE_FOREGROUND_HERO             = true                  //Set to false if no foreground hero should appear on preselection.
        constant boolean CREATE_BACKGROUND_HEROES           = true                  //Set to true if players should be able to see other players' picks in the background.
    endglobals
    //=================================================================================================================================
    globals
        string array CATEGORY_NAMES                         //Names of the hero categories that appear in the hero selection menu.
        integer array PAGE_OF_CATEGORY                      //Set the page at which that category appears within the menu. When there is more than one page, page cycle buttons appear in the menu.
        boolean array PLAYER_SELECTS_HERO                   //List of all players that are supposed to participate in hero selection (true = participates).
        integer array TEAM_OF_PLAYER                        //Team of player, 1, 2 etc. 0 = no teams.
        string array PLAYER_COLOR                           //Hex color string (including "|cff") for each player.
    endglobals
    function SetCategories takes nothing returns nothing
        //Set the names of the categories in the hero selection menu. Starts at index 1. Set to null for no category caption.
        set CATEGORY_NAMES[1]                       = "|cffffcc00Strength|r"
        set CATEGORY_NAMES[2]                       = "|cffffcc00Agility|r"
        set CATEGORY_NAMES[3]                       = "|cffffcc00Intelligence|r"
        set CATEGORY_NAMES[4]                       = "|cffffcc00Reborn|r"
        set CATEGORY_NAMES[5]                       = "|cffffcc00Bleach|r"
        //Set the pages of the categories in the hero selection menu. Pages start at 1.
        set PAGE_OF_CATEGORY[1]                     = 1
        set PAGE_OF_CATEGORY[2]                     = 2
        set PAGE_OF_CATEGORY[3]                     = 3
        set PAGE_OF_CATEGORY[4]                     = 4
        set PAGE_OF_CATEGORY[5]                     = 5
    endfunction
    function HeroSelectionInitPlayers takes nothing returns nothing
        //Set the list of players for which hero selection is enabled (only if AUTO_SET_SELECTING_PLAYERS = false). It is disabled here because computer players must be added to the hero selection.
        set PLAYER_SELECTS_HERO[0]                  = true
        set PLAYER_SELECTS_HERO[1]                  = true
        set PLAYER_SELECTS_HERO[2]                  = true
        set PLAYER_SELECTS_HERO[3]                  = false
        set PLAYER_SELECTS_HERO[4]                  = true
        set PLAYER_SELECTS_HERO[5]                  = true
        set PLAYER_SELECTS_HERO[6]                  = true
        set PLAYER_SELECTS_HERO[7]                  = false
        set PLAYER_SELECTS_HERO[8]                  = true
        set PLAYER_SELECTS_HERO[9]                  = true
        set PLAYER_SELECTS_HERO[10]                 = true
        set PLAYER_SELECTS_HERO[11]                 = false
 
        //Set the teams of players. Players with team 0 are enemies to all players.
        set TEAM_OF_PLAYER[0]                       = 1
        set TEAM_OF_PLAYER[1]                       = 1
        set TEAM_OF_PLAYER[2]                       = 1
        set TEAM_OF_PLAYER[3]                       = 1
        set TEAM_OF_PLAYER[4]                       = 2
        set TEAM_OF_PLAYER[5]                       = 2
        set TEAM_OF_PLAYER[6]                       = 2
        set TEAM_OF_PLAYER[7]                       = 2
        set TEAM_OF_PLAYER[8]                       = 3
        set TEAM_OF_PLAYER[9]                       = 3
        set TEAM_OF_PLAYER[10]                      = 3
        set TEAM_OF_PLAYER[11]                      = 3
    endfunction
    //=================================================================================================================================
    globals
        real array TEAM_FOREGROUND_HERO_X                   //x-Position of foreground hero.
        real array TEAM_FOREGROUND_HERO_Y                   //y-Position of foreground hero.
        real array TEAM_HERO_SELECTION_ANGLE                //Hero selection camera yaw in degrees (0 = facing east, 90 = facing north etc.).
    endglobals
    function HeroSelectionInitArrays takes nothing returns nothing
        //If you set SEPARATE_LOCATIONS_FOR_EACH_TEAM = true, initialize the above array variables here.
    endfunction
 
    function GetPlayerBackgroundHeroLocation takes integer playerIndex, integer whichSlot, integer numberOfPlayers, integer whichTeam, integer slotInTeam, integer teamSize returns location
        local real deltaHorizontal
        local real deltaVertical
        //=================================================================================================================================
        /*
        CREATE_BACKGROUND_HEROES only.
        Here you can customize the positions of the background heroes. Init loops through and calls this function for each player.
        Input:
        playerIndex
        whichSlot               The position of the player when enumerating all players for which hero selection is enabled.
        numberOfPlayers         The number of players for which hero selection is enabled.
        whichTeam               The team the player is assigned to. Discard if there are no teams.
        slotInTeam              The position of that player within his or her team. Discard if there are no teams.
        teamSize                The size of the player's team. Discard if there are no teams.
        Output:
        deltaVertical           The distance between the background hero and the camera eye position along the camera viewing direction.
        deltaHorizontal         The offset between the background hero and the camera eye position perpendicular to the camera viewing direction.
        */
        //==================================================================================================================================
        //Example code:
        local real angle
        local real dist
        local real BACKGROUND_HERO_OFFSET_BACKROW = 1425
        local real BACKGROUND_HERO_OFFSET_FRONTROW = 1250
        if teamSize == 1 then
            set angle = 9
            set dist = BACKGROUND_HERO_OFFSET_BACKROW
        elseif slotInTeam == 1 then
            set angle = 5
            set dist = BACKGROUND_HERO_OFFSET_BACKROW
        elseif slotInTeam == 2 then
            set angle = 13
            set dist = BACKGROUND_HERO_OFFSET_BACKROW
        else
            set angle = 9
            set dist = BACKGROUND_HERO_OFFSET_FRONTROW
        endif
     
        if whichTeam == 2 then
            set angle = -angle
        endif
        set deltaHorizontal = Sin(Deg2Rad(angle))*dist
        set deltaVertical = Cos(Deg2Rad(angle))*dist
        //==================================================================================================================================
        return Location(deltaHorizontal, deltaVertical)
    endfunction


    //==================================================================================================================================
    //                                                  F I N E - T U N I N G
    //==================================================================================================================================


    globals
        //How hero selection looks like before it is enabled (you can ignore this if you plan on enabling hero selection right away).
        constant boolean SHOW_MENU_BEFORE_ENABLED           = false                 //Set to true if players should be able to see the hero selection menu before hero selection is enabled.
        constant boolean PRE_SELECT_BEFORE_ENABLED          = false                 //Set to true if players should be able to pre-select heroes before hero selection is enabled.
     
        //Camera setup (ENFORCE_CAMERA).
        constant real CAMERA_PITCH                          = 12                    //Hero selection camera angle of attack in degrees. 0 = facing horizon, 90 = facing ground.
        constant real CAMERA_DISTANCE                       = 1300                  //Hero selection camera distance from camera target.
        constant real CAMERA_TARGET_OFFSET                  = 500                   //Distance between foreground hero and camera target. Positive = Camera target is behind the hero.
        constant real CAMERA_PERPENDICULAR_OFFSET           = 0                     //Shifts the camera left (negative) or right (positive) with respect to the foreground hero.
        constant real CAMERA_Z_OFFSET                       = 100                   //Hero selection camera z-offset.
        constant real CAMERA_FIELD_OF_VIEW                  = 70                    //Hero selection camera field of view.
        //Sounds and visuals of hero pre-selection (CREATE_FOREGROUND_HERO).
        constant string PRESELECT_EFFECT                    = null                  //Special effect played on the hero's position for the triggering player when switching to a new hero during pre-selection. Set to null for no effect.
        constant boolean PLAY_EMOTE_ON_PRESELECT            = true                  //Set to true if the hero should play its selection emote when the player switches to that hero during pre-selection.
        constant boolean PLAY_ANIMATION_ON_PRESELECT        = true                  //Set to true if the hero should play the selection animation when the player switches to that hero during pre-selection.
        constant boolean PHANTOM_HERO_WHEN_CANNOT_BE_PICKED = true                  //Set to true if a hero should be black and transparent when it is pre-selected but cannot be picked.
        constant real FOREGROUND_HERO_Z                     = 0                     //z-Position of foreground hero.
        //Sounds and visuals on hero pick.
        constant string PICK_EFFECT                         = "HolyLight.mdx"       //Special effect played on the hero's position for the triggering player when picking a hero. Set to null for no effect.
        constant string PICK_SOUND                          = "Sound\\Interface\\ItemReceived.flac" //Sound effect played for the triggering player when selecting a hero. Set to null for no sound.
        constant boolean PLAY_EMOTE_ON_PICK                 = false                 //Set to true if a hero should play its selection emote when a player chooses that hero.
        constant boolean PLAY_ANIMATION_ON_PICK             = false                 //Set to true if a hero should play the selection animation when a player chooses that hero.
        constant real PLAYER_PICK_ESCAPE_DELAY              = 4.0                   //Delay between selecting a hero and being kicked out of hero selection (ESCAPE_PLAY_AFTER_SELECTING only).
        constant real FOREGROUND_HERO_FADEOUT_DELAY         = 1.0                   //The time it takes for the foreground hero to start fading out after being selected.
        constant real FOREGROUND_HERO_FADEOUT_TIME          = 1.5                   //The time it takes for the foreground hero to fade out after being selected.
        constant string OTHER_PLAYER_HERO_PICK_SOUND        = "Sound\\Interface\\InGameChatWhat1.flac"  //Sound played when another player picks a hero. Set to null for no sound.
     
        //Text messages on hero pick.
        constant boolean CREATE_TEXT_MESSAGE_ON_PICK        = true                  //Set to true if a text message should be sent to all other players when a hero is picked (except to enemies when concealed).
        constant real TEXT_MESSAGE_X_OFFSET                 = 0.35                  //x-Offset of text messages from default. Text messages will still suck. Recommend using NeatMessages.
        constant real TEXT_MESSAGE_Y_OFFSET                 = 0                     //y-Offset of text messages from default.
        constant boolean USE_HERO_PROPER_NAME               = false                 //Instead of the hero's name, its proper name will be displayed in the text message. For example, "Uther" instead of "Paladin". Will temporarily create a hero to get the proper name.
        constant boolean MESSAGE_EVEN_WHEN_CONCEALED        = true                  //Set to true if players should still get a message notifying that a player has picked a hero even when it is concealed which hero was picked (CONCEAL_HERO_PICKS_FROM_ENEMIES).
        constant boolean INCLUDE_PROGRESSION_IN_MESSAGE     = false                 //Set to true if the displayed text message should include how many players have selected their hero.
        //Sounds and visuals of background heroes (CREATE_BACKGROUND_HEROES).
        constant real BACKGROUND_HERO_FADEIN_TIME           = 1.0                   //The time it takes for the background hero to fade in after being selected.
        constant boolean PLAY_EMOTE_ON_BACKGROUND_HERO      = true                  //Set to true if a hero should play its selection emote for all other players as it fades in.
        constant boolean PLAY_ANIMATION_ON_BACKGROUND_HERO  = true                  //Set to true if the background hero should play its selection animation as it fades in.
        constant string BACKGROUND_HERO_FADEIN_EFFECT       = "HolyLightRoyal.mdx"  //Special effect played on the background hero as it fades in.
        constant string BACKGROUND_HERO_SELF_HIGHLIGHT      = "RadianceHoly.mdx"    //Special effect added at the location of a player's own background hero to highlight it. Set to null for no highlight.
        constant real BACKGROUND_HERO_HIGHLIGHT_Z           = 70                    //z-Position of background hero self highlight.
        constant real BACKGROUND_HERO_FACING_POINT_OFFSET   = -500                  //Adjusts where the background heroes face. 0 = Background heroes will face the foreground hero. Negative value = Face a point closer to the camera. Positive value = Face a point further from the camera.
        constant string CONCEALED_HERO_EFFECT               = "Objects\\InventoryItems\\QuestionMark\\QuestionMark.mdl" //Model for the background hero seen by a player for which the hero pick was concealed.
        //Last player picks a hero.
        constant real LAST_PLAYER_SELECT_END_DELAY          = 6.0                   //The amount of time after the last player selects a hero and the hero selection ends (ignore if ESCAPE_PLAYER_AFTER_SELECTING)
        constant boolean DELETE_BACKGROUND_HEROES_AFTER_END = false                 //Set to false if background heroes should not get removed when hero selection ends. For example, when a player repicks, they are still there.
        //Layout of hero selection menu.
        constant integer MENU_NUMBER_OF_COLUMNS             = 2                     //Number of hero buttons per row.
        constant real MENU_X_LEFT                           = 0                     //x-Position of left edge of hero selection menu.
        constant real MENU_Y_TOP                            = 0.55                  //y-Position of top edge of hero selection menu.
        constant real MENU_BUTTON_SIZE                      = 0.039                 //Size of individual hero buttons.
        constant real MENU_LEFT_RIGHT_EDGE_GAP              = 0.02                  //Gap between left and right edges of menu and first and last buttons.
        constant real MENU_TOP_EDGE_GAP                     = 0.015                 //Gap between top edge of menu and first button/first category title.
        constant real MENU_BOTTOM_EDGE_GAP                  = 0.02                  //Gap between bottom edge of menu and last button.
        constant real MENU_BUTTON_BUTTON_GAP                = 0.005                 //Gap between two individual hero buttons.
        constant real MENU_CATEGORY_FONT_SIZE               = 16                    //Font size of category titles.
        constant real MENU_CATEGORY_GAP                     = 0.028                 //Gap between buttons of two different categories.
        constant real MENU_CATEGORY_TITLE_Y                 = -0.001                //y-Position shift between category title and center of gap between categories.
        constant real MENU_BORDER_TILE_SIZE                 = 0.03                  //This rounds up the width and height of the menu to an integer multiple of the specified number. This is useful if you're using a tiled border texture, so that there's no discontinuity in the texture.
                                                                                    //Value should be equal to BackdropCornerSize in the HeroSelectionMenu.fdf. Set to 0 to disable.
        constant real MENU_HEROES_RANDOM_GAP                = 0.02                  //Additional gap between last hero button and random pick button.
     
        //Select button.
        constant string SELECT_BUTTON_TEXT                  = "Accept"              //The text in the select hero button at the bottom of the menu.
        constant real SELECT_BUTTON_SCALE                   = 1.0                   //The scale of the select hero button at the bottom of the menu.
        constant real SELECT_BUTTON_WIDTH                   = 0.092                 //The width of the select hero button at the bottom of the menu.
        //Display of random options.
        constant string RANDOM_HERO_TOOLTIP                 = "Choose a random hero."
        constant string SUGGEST_RANDOM_TOOLTIP              = "Suggest a random hero, but don't select it just yet."
        constant string RANDOM_HERO_ICON                    = "ReplaceableTextures\\CommandButtons\\BTNRandomIncredibleIcon.blp"
        constant string SUGGEST_RANDOM_ICON                 = "ReplaceableTextures\\CommandButtons\\BTNSelectHeroOn.blp"
        constant boolean RANDOM_SELECT_CYCLE_STYLE          = true                  //Set to true if the foreground hero should cycle randomly between different heroes while random hero is pre-selected. Set to false if a question mark should be shown.
        constant real RANDOM_SELECT_CYCLE_INTERVAL          = 0.2                   //How fast the heroes are cycled through when random hero is pre-selected (RANDOM_SELECT_CYCLE_STYLE).
     
        //Layout of ability preview buttons.
        constant real HERO_ABILITY_PREVIEW_BUTTON_X         = 0.0                   //x-Position of topmost ability preview button relative to topright corner of menu.
        constant real HERO_ABILITY_PREVIEW_BUTTON_Y         = -0.016                //y-Position of topmost ability preview button relative to topright corner of menu.
        constant real HERO_ABILITY_PREVIEW_BUTTON_SIZE      = 0.025                 //The size of the hero ability preview buttons that appear on the right side of the menu when pre-selecting a hero.
        constant boolean ABILITY_BUTTON_HORIZONTAL_LAYOUT   = false                 //Set to true if ability preview buttons should be arranged horizontally instead of vertically.
        constant string HERO_ABILITY_LEVEL_DELIMITER        = " - ["                //To get the name of a hero ability without the level-text from the tooltip (such as "Stormbolt - [Level 1]" -> "Stormbolt"). This string is used to detect where the name of the ability ends. Set to null if not necessary.
        //Page cycle buttons (when multiple pages are set).
        constant real MENU_PAGE_DOWN_X                      = 0.03                  //x-Position of the page cycle down button relative to bottomleft corner of menu.                    
        constant real MENU_PAGE_DOWN_Y                      = 0.03                  //y-Position of the page cycle down button relative to bottomleft corner of menu.
        constant real MENU_PAGE_UP_X                        = 0.03                  //x-Position of the page cycle up button relative to bottomright corner of menu.
        constant real MENU_PAGE_UP_Y                        = 0.03                  //y-Position of the page cycle up button relative to bottomright corner of menu.
        constant string MENU_PAGE_DOWN_ICON                 = "ReplaceableTextures\\CommandButtons\\BTNNagaBurrow.blp"
        constant string MENU_PAGE_UP_ICON                   = "ReplaceableTextures\\CommandButtons\\BTNNagaUnBurrow.blp"
        //Display of big caption.
        constant string HERO_SELECTION_CAPTION              = "Choose your Hero!"   //The text displayed on the screen during hero selection. Set null to omit text.
        constant real CAPTION_FONT_SIZE                     = 30                    //Font size of hero selection caption.
        constant real CAPTION_X                             = 0.4                   //x-Position of caption center.
        constant real CAPTION_Y                             = 0.41                  //y-Position of caption center.
        constant string CAPTION_COLOR_1                     = "|cffffcc00"          //Caption will cycle between color 1 and color 2. Set the same for no color cycling.
        constant string CAPTION_COLOR_2                     = "|cffffffff"          //Caption will cycle between color 1 and color 2. Set the same for no color cycling.
        constant integer CAPTION_ALPHA_1                    = 255                   //Caption will cycle between alpha 1 and alpha 2. Set the same for no alpha cycling.
        constant integer CAPTION_ALPHA_2                    = 255                   //Caption will cycle between alpha 1 and alpha 2. Set the same for no alpha cycling.
        constant real CAPTION_CYCLE_TIME                    = 4.0                   //The time it takes for the caption to cycle between color 1/2 and alpha 1/2.
        constant real CAPTION_FADEOUT_TIME                  = 2.0                   //The time it takes for the caption to fade out after a player has picked a hero. Set to -1 for no fade out.
 
        //Position of hero button and ability preview button mouse-over tooltips.
        constant real TOOLTIP_LEFT_X                        = 0.51
        constant real TOOLTIP_Y                             = 0.13
        constant boolean TOOLTIP_LOCK_TOP                   = false                 //Set to true if TOOLTIP_Y should refer to top instead of bottom edge.
        constant real TOOLTIP_WIDTH                         = 0.29
        //Display of countdown timer.
        constant string TIMER_TEXT                          = "Time Remaining: |cffffcc00"  //Text before the time remaining display
        constant string TIMER_BAN_PHASE_TEXT                = "Ban Phase: |cffffcc00"       //Text before the time remaining display during the ban phase.
        constant real TIMER_FONT_SIZE                       = 11                    //Font size of the time remaining display.
        constant real TIMER_X                               = 0.4                   //x-Position of center of the time remaining display.
        constant real TIMER_Y                               = 0.59                  //y-Position of the center of the time remaining display.
        //Shadows of heroes.
        constant boolean CREATE_SHADOWS                     = false                 //Create shadows with destructables for heroes (since they are special effects and don't have shadows).
        constant integer SHADOW_DESTRUCTABLE_ID             = 'Dsha'                //Destructable id of the shadow that's created for heroes.
        constant integer NO_SHADOW_DESTRUCTABLE_ID          = 'Dnsh'                //Dummy destructable without a shadow.
    endglobals
    //=================================================================================================================================
    function HeroSelectionSetPlayerColors takes nothing returns nothing
        //Set the colors of players shown for their names in text messages.
        set PLAYER_COLOR[0]                         = "|cffff0402"
        set PLAYER_COLOR[1]                         = "|cff1052ff"
        set PLAYER_COLOR[2]                         = "|cff1BE6BA"
        set PLAYER_COLOR[3]                         = "|cff8530b1"
        set PLAYER_COLOR[4]                         = "|cfffffc00"
        set PLAYER_COLOR[5]                         = "|cffff8a0d"
        set PLAYER_COLOR[6]                         = "|cff20bf00"
        set PLAYER_COLOR[7]                         = "|cffE35BAF"
        set PLAYER_COLOR[8]                         = "|cff949697"
        set PLAYER_COLOR[9]                         = "|cff7EBFF1"
        set PLAYER_COLOR[10]                        = "|cff106247"
        set PLAYER_COLOR[11]                        = "|cff4F2B05"
        set PLAYER_COLOR[12]                        = "|cff9C0000"
        set PLAYER_COLOR[13]                        = "|cff0000C2"
        set PLAYER_COLOR[14]                        = "|cff00EBEB"
        set PLAYER_COLOR[15]                        = "|cffBE00FF"
        set PLAYER_COLOR[16]                        = "|cffECCC86"
        set PLAYER_COLOR[17]                        = "|cffF7A48B"
        set PLAYER_COLOR[18]                        = "|cffBFFF80"
        set PLAYER_COLOR[19]                        = "|cffDBB8EC"
        set PLAYER_COLOR[20]                        = "|cff4F4F55"
        set PLAYER_COLOR[21]                        = "|cffECF0FF"
        set PLAYER_COLOR[22]                        = "|cff00781E"
        set PLAYER_COLOR[23]                        = "|cffA46F34"
    endfunction
endlibrary

Sure :)

edit: I noticed
FOREGROUND_HERO_X &
FOREGROUND_HERO_Y are definitely wrong values 💀
Whats foreground hero? the hero that shows the 3d model and stuff?
 
Oh, I thought you were refering to the fact that the warden is showing up multiple times.

The buttons overlapping isn't really a bug. You have to adjust the positions of those buttons manually. It should look better if you increase MENU_BOTTOM_EDGE_GAP.

As I mentioned, the layout of the page cycle buttons is still wonky. I will make preset options instead of requiring the user to manually adjust their positions. I will also include the option to change them to text buttons.

FOREGROUND_HERO_X/Y is the position of the hero that you see when you preselect. The camera position is not the same, but shifted by CAMERA_TARGET_OFFSET.

Regarding your layout suggestions, seeing hero picks as icons on the top is something that I thought of as well. Including the option to change the random button to a text button next to the select button is also something I should be able to do easily.
 
Level 7
Joined
Sep 16, 2016
Messages
185
An update with the page cycle button style options and a few other improvements.

I still haven't figured out how one moves a complex frame like this into the widescreen area with simpleframes. Any help on this would be appreciated!
@Tasyen Perhaps you might know?
 

Wrda

Spell Reviewer
Level 26
Joined
Nov 18, 2012
Messages
1,888
@Antares A I have some questions regarding the UI :grin:

By the way, I love the setup of the code so far, just wondering how feasible would a layout for the UI be for something like this with this system? A dropdown could also work to avoid category buttons but both would work nicely, I guess with a dropdown you save more space on the selection screen
View attachment 461709

With this, only the sound for the hero picked played for the player who picked it, so you don't hear the other unit sounds

This is one of the bugs with UI I noticed

:sad:
Player selected hero model isn't that feasible, it requires the model to have no glow, no particle emitters and a few other things.
It is kind of out of the scope for this :p
 
Level 7
Joined
Sep 16, 2016
Messages
185
I meant when you click on the frame, you create a dummy unit using that model, I think it is feasible, I have it in my current map :p But so does this system have
 
Could you perhaps add Wurst too? </3
It has been translated into all relevant languages. :ogre_hurrhurr:

I mean, what's the point of Wurst these days? You have GUI for the people who just want to throw together a map or two. You have vJASS for the people who are a bit more into programming and then you have Lua for the people who want to take the time to learn a proper language, set up their IDE etc. Where does Wurst fit into this?
 
Level 7
Joined
Sep 27, 2010
Messages
54
I have tried to add this system to my map GUI, but it does not detect the library in the trigger with the imported file, In the end the editor ends up crashing.
or if you can make a video on how to import it in GUI, please.
 
I have tried to add this system to my map GUI, but it does not detect the library in the trigger with the imported file, In the end the editor ends up crashing.
or if you can make a video on how to import it in GUI, please.
I think you need to enable the option in the editor "Automatically create unknown variables while pasting trigger data". You find it under File -> Preferences. I did that and then I copy pasted the Hero Selection parent folder and it worked for me.

Tell me if this works for you as well. Unfortunately, I'm not an expert in GUI. Just recently started translating my resources.
 

Attachments

  • unknownVariables.png
    unknownVariables.png
    116.6 KB · Views: 4
Finally figured out widescreen support. Here is version 1.3:
  • Improved GUI documentation and import instructions.
  • Enabled hero selection menu and tooltips to be put into the widescreen area. For the menu, this is done by setting MENU_X_LEFT to a negative number. The menu will automatically be adjusted for players who aren't using widescreen monitors.
  • Added the option to add player name texttags over the background heroes. The option in the config is PLAYER_TEXT_TAGS. If you have already customized the Config and don't want to overwrite it, simply add the line below to the Config under "Sounds and visuals of background heroes."
vJASS:
constant boolean PLAYER_TEXT_TAGS					= true					//Create text tags that show the players' names over the background heroes.
 
Level 6
Joined
Nov 18, 2014
Messages
21
Wow, this look great ! :eek:2
Now that I have seen that, clicking on a hero to pick him seems a bit... bland :grin:


I certainly want to add this on the map I'm working on. I have a few questions:
  • Can a player "leave" selection screen when he picked his hero without waiting the others ?
  • Can a player re-enter the selection screen without triggering selection for every player ?

And here comes the part where I'm under heavy copium
  • Could you be a fantastic developer, and add support for multiple picks ? :cute: There is a dual mode where players can pick 2 heroes, and I guess it could come handy for other maps as well. When selecting a hero, previous ones would be pushed on the side/on the back, or something like that.


Anyway, it looks neat, great job :thumbs_up:
 
Thank you!

Yes, you can have a player leave hero selection without waiting for others by setting ESCAPE_PLAYER_AFTER_SELECTING to true. There is, however, right now a potential desync associated with that setting. It should be easy enough for me to fix, I just haven't gotten to it yet.

A player can also re-enter hero selection with PlayerReturnToHeroSelection.

Regarding your request, I'd have to think about how to accomplish that. There's the "good enough" implementation, which should be doable for me in a reasonable amount of time, and then there's the perfect solution, which would need me to rewrite large chunks of the system.

I can probably modify the system so that a player can select multiple heroes and leave all the visual effects stuff up for you to deal with with the custom code you can write. I don't know, it's tough. I have to think about it.

Which language are you using?
 
Level 6
Joined
Nov 18, 2014
Messages
21
Yes, you can have a player leave hero selection without waiting for others by setting ESCAPE_PLAYER_AFTER_SELECTING to true. There is, however, right now a potential desync associated with that setting. It should be easy enough for me to fix, I just haven't gotten to it yet.
Fixing it would be great indeed. Desync are not something I want to tackle with :con:

A player can also re-enter hero selection with PlayerReturnToHeroSelection.

So, if I'm not mistaken, hero selection will be running in background for the whole game ?
Or does it "end" when every player has picked, and thus I need to restart it when a player want to return to selection ?

Regarding your request, I'd have to think about how to accomplish that. There's the "good enough" implementation, which should be doable for me in a reasonable amount of time, and then there's the perfect solution, which would need me to rewrite large chunks of the system.

I can probably modify the system so that a player can select multiple heroes and leave all the visual effects stuff up for you to deal with with the custom code you can write. I don't know, it's tough. I have to think about it.

Which language are you using?

I'm using GUI/Jass.

As I already have a (dusty and old) selection system, I can wait for a revamp to get the perfect solution :grin:
If you think it will be too hard / annoying to do, don't worry about me, I knew I was coping :xxd:
 
Fixing it would be great indeed. Desync are not something I want to tackle with :con:
I will look into it soon.

So, if I'm not mistaken, hero selection will be running in background for the whole game ?
Or does it "end" when every player has picked, and thus I need to restart it when a player want to return to selection ?
There is the DELETE_BACKGROUND_HEROES_AFTER_END option. If you disable it, the heroes that other players picked will remain after hero selection ends. If you enable it, the heroes will be removed, so if a player returns to it to repick, they will be gone, which might be undesirable.

I don't quite know what you mean with "running in background." There's no script running in the background that's draining resources, if that's what you mean, but you can choose if the effects get cleaned up or not.

As I already have a (dusty and old) selection system, I can wait for a revamp to get the perfect solution :grin:
If you think it will be too hard / annoying to do, don't worry about me, I knew I was coping :xxd:
I won't be making a perfect solution that works in all combinations of settings, but if what you need is doable in a reasonable amount of time, I'm open to it.
 
Level 6
Joined
Nov 18, 2014
Messages
21
There is the DELETE_BACKGROUND_HEROES_AFTER_END option. If you disable it, the heroes that other players picked will remain after hero selection ends. If you enable it, the heroes will be removed, so if a player returns to it to repick, they will be gone, which might be undesirable.

I don't quite know what you mean with "running in background." There's no script running in the background that's draining resources, if that's what you mean, but you can choose if the effects get cleaned up or not.

Sorry, bad choice of words here. I meant, there isn't anything reset when selection end, so you can resume it whenever you want (variable in the same state, picked heroes unavailable etc.).
As I go a bit of spare time, I played more with demo and read more of the code, and I see you have that with repick, which is perfect for me. My apologies for not seeing that earlier.

I won't be making a perfect solution that works in all combinations of settings, but if what you need is doable in a reasonable amount of time, I'm open to it.

Well, my need would be the following:
  • Being able to launch single or duo hero selection
  • In duo mode, when clicking on "accept", your hero goes on your circle, and you can select an other hero. When both hero are picked, I think the result would look like attached picture (for red).
    Malfurion being the first picked hero, Maeiv the second
    1713982848078.png
  • Repick would obviously reset both heroes and allow 2 selections again.
  • A "Both random" button would be a nice bonus too.
Aside from the duo mode, I would live to see those addition (I tried to see if they were available but couldn't find them, hope I'm not just blind :grin: )
  • Function/variable available after selection to know if players selected randomly their heroes
  • Function/variable available after selection to know if players repicked their heroes
  • An option to force random selection for all players (but still having the random animation for a few seconds before everyone leave selection), with possibility to have a forced random or free repick.

I hope I'm not too annoying :gg:
 
You have two ways to return to hero selection. In the test map, you can try out the two options on the goblin in the forest to see the difference. When you repick, you won't get the "Heroes have gathered blahblah..." text because that's triggered by BeginHeroSelection. When you restart, you get the text, because BeginHeroSelection is called again.

Doing the hero visual stuff would be a lot of work. If you can take care of those, that will be much more doable because you can just put in the numbers that you need for your specific selection screen and they don't need to make sense for every single combination of parameters someone might use.

There is currently no function to query if a hero was picked randomly or repicked. Adding those will be no problem. I'll include them in the next version!

I hope I'm not too annoying :gg:
Not at all :psmile:. It's nice that this generates some interest and I receive feedback.
 
Level 6
Joined
Nov 18, 2014
Messages
21
Doing the hero visual stuff would be a lot of work. If you can take care of those, that will be much more doable because you can just put in the numbers that you need for your specific selection screen and they don't need to make sense for every single combination of parameters someone might use.

If you can allow multiple selections via a parameters like HEROES_PER_PLAYERS, and have arrays for X and Y positions so we can tweak them and get what we want, that should be enough. No need for you to compute them all :xxd:

There is currently no function to query if a hero was picked randomly or repicked. Adding those will be no problem. I'll include them in the next version!
Great news :thumbs_up:
 
Version 1.3.2:

  • Fixed a bug that could cause a desync due hiding/showing ConsoleUIBackdrop locally.
  • Two additional arguments are passed to the HeroSelectionOnPick callback function. These are wasRandomPick and wasRepick.

To update with an already existing configuration, replace the main script as well as the HeroSelectionOnPick function (this is going to become a problem, isn't it?)
 
Top