• 🏆 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!

HeroSelector

This bundle is marked as pending. It has not been reviewed by a staff member yet.

Introduction


HeroSelector is a Frame-UI System to pick a hero. Provides a vjass and Lua version. It features Random, Picking, Baning, unique/limited Picks, Random-Only "Picks", Swap, simple Pick Requirement, Categories. Picking/Banning can be enabled or disabled for a subgroup of Players at a wanted moment. The baned heroes can be baned instantly or wait until the next Picking.
A Pick Requirement enables a Hero only for a Player, a TeamNr, a force, a Race or for a TechLevel/UnitCode-count. But each option can have only one requirement. The button is still visible for all Players.

Category can be clicked to have Heros not having that category being displayed less visible. One should use power 2 numbers when Setting/adding categories to selection Options. All added Options get on Default the category melee or ranged or None if the can't attack on default.

The Selection pool is displayed in one page in rows and cols the amount of cols and rows can be changed inside HeroSelectors Setup which is at the top of HeroSelector after the API comments. The space between 2 rows and 2 Buttons can be changed as well as the button size.

HeroSelector Trigger Files


A short overview of the Trigger Editor files
HeroSelectors features are split into 3/(4 jass) Trigger Editor Files.
HeroSelector is the main one the big box with the hero icons and categories.
Teamviewer manages the herocards that show current preview.
HeroInfo shows the info for the current selected preview.
Each of them has a setup part at the top (globals vjass + some functions)

(vjass) HeroSelectorAction is the place for manual (v)jass writers to change the default behaviour of HeroSelectorEvents and the initial selectable options setup

How to use the Selection grid?


Each hero can only be once in the grid. When using HeroSelector.addUnit it will add a new slot. There are HeroSelector.ButtonColCount*HeroSelector.ButtonRowCount slots.
3 rows with 4 cols would result into:

Code:
01,  02,  03,  04,
05,  06,  07,  08,
09,  10,  11,  12,

When you want to leave fields in the grid empty use HeroSelector.addUnit(0) or HeroSelector.addUnit().
There is a GUI setup which works with indexes, not set indexes will be empty fields.

Hook into


Offers 4 functions one can hook into to add custom Code without having to touch the System Code. By doing that one can add extra Features. Like shown with TeamView and HeroInfo which are not part of the core System but in the same folder. Although this functions have in this upload some content which I think is useful to have. They are below HeroSelector Setup.
Lua:
function HeroSelector.unitCreated(player, unit, isRandom)
--    this function is called when an unit is picked, add here you actions that have to be done for the picked unit
function HeroSelector.buttonSelected(player, unitCode)
--    this function is called when an player selects an button, this is not the picking.
function HeroSelector.unitBaned(player, unitCode)
--    this function is called when a player bans an unitCode.
function HeroSelector.initHeroes()
--    this function will be called before anything is created, when not using GUI to setup data you could add the selectable heroes here.
function HeroSelector.repick(unit[, player])
-- if you hook/overwritte this, make sure you run it.

How to Install:


You map has to be in Lua-Mode (Lua mode).
jass requires vjass enabled
Export and Import
war3mapImported\HeroSelector.fdf
war3mapImported\HeroSelector.toc
war3mapImported\HeroSelectorBan.mdx
WHEN USING THE EXPORT ALL BUTTON, IT CAN HAPPEN THAT THE CONTENT OF the fdf and toc FILES SWAP
Copy the trigger Folder "HeroSelector" into your map.

API:


Lua:
--[[
HeroSelector V1.8b by Tasyen

A UI System to pick a Hero/UnitCode also supports ban and requirments
]]
HeroSelector = {}
HeroSelector.TocPath                = "war3mapImported\\HeroSelector.toc" --ex/import also "HeroSelector.fdf"
--Box
HeroSelector.BoxFrameName           = "HeroSelectorRaceBox" --this is the background box being created
HeroSelector.BoxPosX                = 0.3
HeroSelector.BoxPosY                = 0.4
HeroSelector.BoxPosPoint            = FRAMEPOINT_CENTER
HeroSelector.AutoShow               = true --(true) shows the box and the Selection at 0.0 for all players
HeroSelector.AutoCreate             = true --(true) create itself when gameStarted. (false) you need to HeroSelector.initHeroes() HeroSelector.initFrames()
--Unique Picks
HeroSelector.UnitCount              = 999 --each hero is in total allowed to be picked this amount of times (includes random, repicking allows a hero again).
HeroSelector.UnitCountPerTeam       = 1 --Each Team is allowed to pick this amount of each unitType
HeroSelector.ToManyTooltip          = "OUTOFSTOCKTOOLTIP"
-- HeroSelector.PickAbleHeroes is a Way to define pickable Units without touching HeroSelector.initHeroes
-- pickable options expects one string like "Hamg,Hmkg,Hpal,Hblm". It can contain 0 to have empty slots "Hamg,0,Hpal,0,Hblm"
-- all melee heroes "Hamg,Hmkg,Hpal,Hblm,Obla,Ofar,Otch,Oshd,Udea,Ulic,Udre,Ucrl,Edem,Ekee,Emoo,Ewar,Nngs,Nbrn,Npbm,Nplh,Nbst,Nfir,Ntin,Nalc"
HeroSelector.PickAbleHeroes         = ""
HeroSelector.EnableGUICode          = true  -- enables the gui parser and throwing the picked event. if you don't use either you should set this to false.
--Ban
HeroSelector.DelayBanUntilPick      = false --(true) baning will not be applied instantly, instead it is applied when HeroSelector.enablePick is called the next time.
--Category
HeroSelector.CategoryData = {
    --Icon path, tooltip Text (tries to localize), HeroCdes "Hmkg,Hpal"
    {"ReplaceableTextures\\CommandButtons\\BTNSteelMelee", "MELEE"},                 --1, automatic detected when adding an unit
    {"ReplaceableTextures\\CommandButtons\\BTNHumanMissileUpOne", "Ranged"},         --2, automatic detected when adding an unit
    {"ReplaceableTextures\\CommandButtons\\BTNGauntletsOfOgrePower", "STRENGTH"},    --4
    {"ReplaceableTextures\\CommandButtons\\BTNSlippersOfAgility", "AGILITY"},        --8
    {"ReplaceableTextures\\CommandButtons\\BTNMantleOfIntelligence", "INTELLECT"},   --16
}
HeroSelector.CategoryAffectRandom   = true  --(false) random will not care about selected category
HeroSelector.CategoryMultiSelect    = false --(false) deselect other category when selecting one, (true) can selected multiple categories and all heroes having any of them are not filtered.
HeroSelector.CategorySize           = 0.02  --the size of the Category Button
HeroSelector.CategorySpaceX         = 0.0008 --space between 2 category Buttons, it is meant to need only one line of Categoryy Buttons.
HeroSelector.CategoryFilteredAlpha  = 45     -- Alpha value of Heroes being filtered by unselected categories
HeroSelector.CategoryAutoDetect     = true  -- try to set category of added options automatic
HeroSelector.CategoryAutoDetectHero = true  -- (heavy performance wise) create and remove added Heroes to setup the Category for the primary Attribute Str(4) Agi(8) Int(16)

--Indicator
HeroSelector.IndicatorPathPick      = "UI\\Feedback\\Autocast\\UI-ModalButtonOn.mdl" --this model is used by the indicator during picking
HeroSelector.IndicatorPathBan       = "war3mapImported\\HeroSelectorBan.mdl" --this model is used by the indicator during baning
--Grid
HeroSelector.SpaceBetweenX          = 0.008 --space between 2 buttons in one row
HeroSelector.SpaceBetweenY          = 0.008 --space between 2 rows
HeroSelector.ButtonColCount         = 4 --amount of buttons in one row
HeroSelector.ButtonRowCount         = 4 --amount of rows
HeroSelector.ChainedButtons         = true --(true) connect to the previous button/ or row, (false) have a offset to the box topLeft in this moving a button has no effect on other buttons.
--Button
HeroSelector.ButtonSize             = 0.036 --size of each button
HeroSelector.ButtonBlendAll         = false --(true) when a hero icon uses transparenzy
HeroSelector.EmptyButtonPath        = "UI\\Widgets\\EscMenu\\Human\\blank-background.blp"
HeroSelector.HideEmptyButtons       = true
--Ban Button
HeroSelector.BanButtonTextPrefix    = "|cffcf2084" --Prefix Text for the Ban Button
HeroSelector.BanButtonText          = "CHAT_ACTION_BAN" --tries to get a Localized String
HeroSelector.BanButtonSizeX         = 0.085
HeroSelector.BanButtonSizeY         = 0.03
HeroSelector.BanTooltip             = "DISALLOWED"
HeroSelector.BanIgnoreRequirment    = true -- (true) Ban is not restricted by Requirments
--BanDone Button
HeroSelector.BanDoneButtonTextPrefix    = "|cffcf2084" --Prefix Text for the Ban Button
HeroSelector.BanDoneButtonText          = "QUESTACCEPT" --tries to get a Localized String
HeroSelector.BanDoneButtonSizeX         = 0.085
HeroSelector.BanDoneButtonSizeY         = 0.03
HeroSelector.BanDoneButtonIsShown       = false --show this button in the Ban Phase
--Accept Button
HeroSelector.AcceptButtonTextPrefix = ""
HeroSelector.AcceptButtonText       = "ACCEPT"
HeroSelector.AcceptButtonSizeX      = 0.085
HeroSelector.AcceptButtonSizeY      = 0.03
HeroSelector.AcceptButtonIsShown    = true --show this button in the Pick Phase
--Random Button
HeroSelector.RandomButtonTextPrefix = ""
HeroSelector.RandomButtonText       = "RANDOM" --tries Localizing
HeroSelector.RandomButtonSizeX      = 0.085
HeroSelector.RandomButtonSizeY      = 0.03
HeroSelector.RandomButtonIsShown    = true --show this button in the Pick Phase
HeroSelector.RandomButtonPick       = false --(true) pressing the random button will pick the option. (false) pressing the random button will select a button, random only heroes can not be selected, but that does not matter. This weak random and randomonly should not be combined.
--Repick Button
HeroSelector.RepickButtonTextPrefix = ""
HeroSelector.RepickButtonText       = "REPICK" --tries Localizing
HeroSelector.RepickButtonTextLimit  = "Repick (#)" --tries Localizing # is the variable for the remaining
HeroSelector.RepickButtonSizeX      = 0.085
HeroSelector.RepickButtonSizeY      = 0.03
HeroSelector.RepickButtonIsShown    = true --show this button in the Pick Phase
HeroSelector.RepickMax              = 0 -- when bigger than 0 only that amount of Repicks is allowed
--ShowButton
HeroSelector.ShowButtonTextPrefix = ""
HeroSelector.ShowButtonText       = "PICK-HERO" --tries Localizing
HeroSelector.ShowButtonSizeX      = 0.085
HeroSelector.ShowButtonSizeY      = 0.03
HeroSelector.ShowButtonIsShown    = false --show this button 
HeroSelector.ShowButtonX          = 0.42
HeroSelector.ShowButtonY          = 0.58
HeroSelector.ShowButtonPoint      = FRAMEPOINT_TOPLEFT
HeroSelector.ShowButtonParent     = function() return BlzGetOriginFrame(ORIGIN_FRAME_GAME_UI, 0) end
--Tooltip
HeroSelector.TooltipPrefix          = "|cffffcc00"
HeroSelector.TooltipOffsetX         = 0
HeroSelector.TooltipOffsetY         = 0
HeroSelector.TooltipPoint           = FRAMEPOINT_BOTTOM --pos the Tooltip with which Point
HeroSelector.TooltipRelativePoint   = FRAMEPOINT_TOP --pos the Tooltip to which Point of the Relative
HeroSelector.TooltipRelativIsBox    = false          --(true) use the box as anchor, (false) use the button as anchor
HeroSelector.TooltipRequires        = "QUESTCOMPONENTS"
HeroSelector.TooltipRequires2       = {
    [RACE_HUMAN] = "HUMAN"
    ,[RACE_ORC] = "ORC"
    ,[RACE_UNDEAD] = "UNDEAD"
    ,[RACE_NIGHTELF] = "NIGHT_ELF"
    ,[PLAYER_STATE_RESOURCE_HERO_TOKENS] = "HERO TOCKEN"
}


--Border
HeroSelector.BorderSize = {}
HeroSelector.BorderSize[RACE_HUMAN]     = 0.029 --border size seen by Race Human, this is needed cause the borders are different in size.
HeroSelector.BorderSize[RACE_ORC]       = 0.029
HeroSelector.BorderSize[RACE_UNDEAD]    = 0.035
HeroSelector.BorderSize[RACE_NIGHTELF]  = 0.035
HeroSelector.BorderSize[RACE_DEMON]     = 0.024


--This runs before the box is created with that the system has the needed data right when it is needed.
--you can add units somewhere else but it is done after the box was created you have to use the update function to update the textures of shown buttons
function HeroSelector.initHeroes()
    --create categories setuped in config
    local categories = HeroSelector.CategoryData
    HeroSelector.Category = {}
    for index, value in ipairs(categories) do
       HeroSelector.addCategory(value[1], value[2])
    end

    --read GUI, when the variable exist
    if HeroSelector.EnableGUICode and udg_HeroSelectorUnitCode then
        local index = 1
        --add from index 1 all random only heroes
        while udg_HeroSelectorRandomOnly[index] ~= 0 do
            HeroSelector.addUnit(udg_HeroSelectorRandomOnly[index], true)
            index = index + 1
        end

        --copy the setuped field
        for index = 1, HeroSelector.ButtonColCount*HeroSelector.ButtonRowCount,1 do
            HeroSelector.addUnit(udg_HeroSelectorUnitCode[index])
            if udg_HeroSelectorCategory and udg_HeroSelectorCategory[index] ~= 0 then
                HeroSelector.addUnitCategory(udg_HeroSelectorUnitCode[index], udg_HeroSelectorCategory[index])
            end
        end

        --kill the tables
        udg_HeroSelectorUnitCode = nil
        udg_HeroSelectorRandomOnly = nil
        udg_HeroSelectorCategory = nil
    end

    --adding further units when using the GUI Array does not make much sense, except you would add rows.

    if HeroSelector.PickAbleHeroes and (type(HeroSelector.PickAbleHeroes) =="string" and string.len( HeroSelector.PickAbleHeroes ) >= 4 ) then HeroSelector.addUnit(HeroSelector.PickAbleHeroes) end

    local iCat = 1
    for index, value in ipairs(categories) do
        if value[3] then
            HeroSelector.addUnitCategory(value[3], iCat)
        end
        iCat=iCat*2
    end

    --skip further demo code
    if true then return end


    HeroSelector.addUnit("Hgam", true, 0) --antonidas is an only random Hero that can only be randomed by team 0 (for users 1).
    HeroSelector.addUnit("Eevi", true, 1) --evil Illidan is an only random Hero that can only be randomed by team 1 (for users 2).
    
    --Adds requirments
    --when you have a ban phase it might be better to add the requirments after the ban phase is over, otherwise one can only ban own options.
    --paladin requires having 5 footman, Archmage a Hero Tocken other human heroes can only picked by human-Race, as nightelf only for Nightelf
    HeroSelector.setUnitReq('Hpal', {FourCC('hfoo'), 5})
    HeroSelector.setUnitReq('Hamg', PLAYER_STATE_RESOURCE_HERO_TOKENS)
    HeroSelector.setUnitReq('Hblm', RACE_HUMAN)
    HeroSelector.setUnitReq('Hmkg', RACE_HUMAN)
    --HeroSelector.setUnitReq('Ofar', RACE_ORC)
    --HeroSelector.setUnitReq('Oshd', RACE_ORC)
    --HeroSelector.setUnitReq('Otch', RACE_ORC)
    --HeroSelector.setUnitReq('Obla', RACE_ORC)
    HeroSelector.setUnitReq('Emoo', RACE_NIGHTELF)
    HeroSelector.setUnitReq('Edem', RACE_NIGHTELF)
    HeroSelector.setUnitReq('Ekee', RACE_NIGHTELF)
    HeroSelector.setUnitReq('Ewar', RACE_NIGHTELF)
    --HeroSelector.setUnitReq('Udea', RACE_UNDEAD)
    --HeroSelector.setUnitReq('Ulic', RACE_UNDEAD)
    --HeroSelector.setUnitReq('Udre', RACE_UNDEAD)
    --HeroSelector.setUnitReq('Ucrl', RACE_UNDEAD)
    --[[
    local categoryMelee = 1 --autodetected
    local categoryRanged = 2 --autodetected
    local categoryStr = 4
    local categoryAgi = 8
    local categoryInt = 16
    HeroSelector.addUnitCategory('Hpal', categoryStr)
    HeroSelector.addUnitCategory('Hamg', categoryInt)
    HeroSelector.addUnitCategory('Hblm', categoryInt)
    HeroSelector.addUnitCategory('Hmkg', categoryStr)
    HeroSelector.addUnitCategory('Ofar', categoryInt)
    HeroSelector.addUnitCategory('Oshd', categoryAgi)
    HeroSelector.addUnitCategory('Otch', categoryAgi)
    HeroSelector.addUnitCategory('Obla', categoryAgi)
    HeroSelector.addUnitCategory('Emoo', categoryAgi)
    HeroSelector.addUnitCategory('Edem', categoryAgi)
    HeroSelector.addUnitCategory('Ekee', categoryInt)
    HeroSelector.addUnitCategory('Ewar', categoryAgi)
    HeroSelector.addUnitCategory('Udea', categoryStr)
    HeroSelector.addUnitCategory('Ulic', categoryInt)
    HeroSelector.addUnitCategory('Udre', categoryStr)
    HeroSelector.addUnitCategory('Ucrl', categoryStr)

    HeroSelector.setUnitCategory('Hgam', categoryInt + categoryRanged)
    HeroSelector.setUnitCategory("Eevi", categoryAgi + categoryMelee)
    --]]
    
    --[[
    HeroSelector.addUnit('Hpal') --add paladin as selectable Hero
    HeroSelector.addUnit('Hamg')
    HeroSelector.addUnit('Hblm')
    HeroSelector.addUnit('Hmkg')
    HeroSelector.addUnit("Obla", true) --this unit can only be randomed
    HeroSelector.addUnit("Ofar")
    HeroSelector.addUnit("Otch", 1) --this unit can only be randomed
    HeroSelector.addUnit() --this is an empty box. It still takes a slot.
    HeroSelector.addUnit() 
    HeroSelector.addUnit("Oshd")
    HeroSelector.addUnit("Edem")
    HeroSelector.addUnit() --this is an empty box. It still takes a slot.
    HeroSelector.addUnit() 
    HeroSelector.addUnit("Ekee")
    HeroSelector.addUnit("Emoo")
    HeroSelector.addUnit("Ewar",true)
    HeroSelector.addUnit("Udea")
    HeroSelector.addUnit("Ulic")
    HeroSelector.addUnit("Udre")
    HeroSelector.addUnit("Ucrl",1)
    --]]
end

function HeroSelector.autoDetectCategory(unitCode)
    if HeroSelector.CategoryAutoDetect then
        if IsUnitIdType(unitCode, UNIT_TYPE_MELEE_ATTACKER) then
            HeroSelector.UnitData[unitCode].Category = 1
        elseif IsUnitIdType(unitCode, UNIT_TYPE_RANGED_ATTACKER) then
            HeroSelector.UnitData[unitCode].Category = 2
        end
        if HeroSelector.CategoryAutoDetectHero and IsUnitIdType(unitCode, UNIT_TYPE_HERO) then
            local unit = CreateUnit(Player(bj_PLAYER_NEUTRAL_EXTRA), unitCode, 0, 0, 270)
            local primaryAttribute = BlzGetUnitIntegerField(unit, UNIT_IF_PRIMARY_ATTRIBUTE)
            RemoveUnit(unit)
            if ConvertHeroAttribute(primaryAttribute) == HERO_ATTRIBUTE_STR then
                HeroSelector.UnitData[unitCode].Category = HeroSelector.UnitData[unitCode].Category + 4
            elseif ConvertHeroAttribute(primaryAttribute) == HERO_ATTRIBUTE_AGI then
                HeroSelector.UnitData[unitCode].Category = HeroSelector.UnitData[unitCode].Category + 8
            elseif ConvertHeroAttribute(primaryAttribute) == HERO_ATTRIBUTE_INT then 
                HeroSelector.UnitData[unitCode].Category = HeroSelector.UnitData[unitCode].Category + 16
            end
        end
    end
end

--what happens to the unit beeing picked, player is the one having pressed the button
function HeroSelector.unitCreated(player, unitCode, isRandom)
    bj_lastCreatedUnit = CreateUnit(player, unitCode, GetPlayerStartLocationX(player), GetPlayerStartLocationY(player), 0)
    local unit = bj_lastCreatedUnit
    HeroSelector.PickedUnit[player] = unit
    if isRandom then
        --randomed
    else
        --picked
    end

    if player == Player(1) then
                
    end

    PanCameraToTimedForPlayer(player, GetUnitX(unit), GetUnitY(unit),0)
    SelectUnitForPlayerSingle(unit, player)
    HeroSelector.enablePick(false, player) --only one pick for this player

    if HeroSelector.EnableGUICode then
        globals.udg_HeroSelectorEvent = 0
        globals.udg_HeroSelectorEvent = 1.0
    end
    --print(GetPlayerName(player),"picks",GetUnitName(unit))
end

--happens when the banButton is pressed, player is the one having pressed the button
function HeroSelector.unitBaned(player, unitCode)
    HeroSelector.enableBan(false, player) --only one ban
    --print(GetPlayerName(player),"bans",GetObjectName(unitCode))
end

function HeroSelector.buttonSelected(player, unitCode)
    --player who pressed the button
    --unitCode the unitCode selected
    --this is not picked.

    --print(GetPlayerName(player),"selects",GetObjectName(unitCode))
end

function HeroSelector.repick(unit, player)
    
    if not unit then return end

    UnitRemoveBuffsBJ(bj_REMOVEBUFFS_ALL, unit) --this is done to undo metamorph
    local unitCode = GetUnitTypeId(unit)
    if unitCode == 0 then return end

    HeroSelector.counterChangeUnitCode(unitCode, -1, player)

    if not player then
        player = GetOwningPlayer(unit)
    end
    HeroSelector.PickedUnit[player] = nil
    HeroSelector.show(true, player)
    HeroSelector.enablePick(true, player)
    RemoveUnit(unit)
end

function HeroSelector.actionBanDoneButton(frame, player)
    -- HeroSelector.enablePick(true) -> on click finish the Ban Phase and start pick Phase
    HeroSelector.enablePick(true)
end

function HeroSelector.actionShowButton(frame, player)
    if GetLocalPlayer() == player then BlzFrameSetVisible(HeroSelector.Box, not BlzFrameIsVisible(HeroSelector.Box)) end
end

--[[
This functions are found directly below the config and belong to the config.
They also can be hooked but you might lose the default. Could do it like it is done in TeamViewer create a Backup of the current then overwrite it and call the backup in the replacement.

function HeroSelector.unitCreated(player, unitCode, isRandom)
    this function is called when an unit is picked, add here you actions that have to be done for the picked unit

function HeroSelector.buttonSelected(player, unitCode)
    this function is called when an player selects an button, this is not the picking.

function HeroSelector.unitBaned(player, unitCode)
    this function is called when a player bans an unitCode.

function HeroSelector.actionBanDoneButton(frame, player)
    this function happens when the banDone Button is clicked

function HeroSelector.repick(unit[, player])
    if player is skiped unit owner sees the selection
    this will remove the unit from the game.
    Adds thie unitcode of the unit to the randompool

function HeroSelector.autoDetectCategory(unitCode)
    this called on every unit added. It is a good place for simple automatic categorizes, on default it categorizes melee as 1 and ranged as 2.

function HeroSelector.initHeroes()
    this function will be called before anything is created, when not using GUI to setup data you could add the selectable heroes here.
------
How use the Selection grid?
Each hero can only be once in the grid. When using HeroSelector.addUnit it will add a new slot. There are HeroSelector.ButtonColCount*HeroSelector.ButtonRowCount slots.
3 rows with 4 cols would result into:
01 02 03 04
05 06 07 08
09 10 11 12

When you want to leave fields in the grid empty use HeroSelector.addUnit(0) or HeroSelector.addUnit().
There is a GUI setup which works with indexes, not set indexes will be empty fields.
------
function HeroSelector.setUnitReq(unitCode, who)
    adds an requirement: can be a player, a force, a teamNumber, a race, a table {techcode, level}, playerState (need to have 1 of it, mostly useful for PLAYER_STATE_RESOURCE_HERO_TOKENS), skip who or nil will remove an requirment.
    unitCode can be a number or a string 'Hpal' or 'Hpal,Ofar,Ulic'
    Only when the local player fullfills than he can click the button.
    calling this will not update the selected buttonIndex of players nor does this update the clickability.
    To update the clickability when setting requirments after the Box was created use HeroSelector.update() and deselect indexes
    won't work when the unitCode wasn't added yet.
    
function HeroSelector.addUnit([unitCode, onlyRandom, requirement])
    unitCode can be a number or a string 'Hpal' or 'Hpal,Ofar,Ulic,0,Obla'
    can be called without arguments to hava a empty slot calling it with 0 has the same effect
    requirement works like who in HeroSelector.setUnitReq.

function HeroSelector.setUnitCategory(unitCode, category)
    unitCode can be a number or a string 'Hpal' or 'Hpal,Ofar,Ulic'
    sets the category of an added Option.
    Category should be a power 2 number. 1 2 4 8 16 32 ....

function HeroSelector.addUnitCategory(unitCode, category)
    unitCode can be a number or a string 'Hpal' or 'Hpal,Ofar,Ulic'
    Keeps previous setings untouched

function HeroSelector.addCategory(icon, text)
    icon is the enabled image, text is the tooltip text.

function HeroSelector.clearUnitData()
    removes all current UnitData this includes limit-counters, requirements, categories.

function HeroSelector.show(flag, [who])
    Shows/Hides HeroSelector to who
    flag = true show it, false = hide it
    who can be a player, a force, a teamNumber, a race or nothing = anyone
    teamNumbers are the warcraft 3 given teamNumbers starting with 0 for team 1.
    the force is expected to be kept alive

function HeroSelector.setFrameText(frame, text[, who])
    uses BlzFrameSetText onto frame when the local player is included in who by the rules of function HeroSelector.includesPlayer
function HeroSelector.setTitleText(text[, who])
    wrapper HeroSelector.setFrameText
function HeroSelector.setBanButtonText(text[, who])
    wrapper HeroSelector.setFrameText
function HeroSelector.setAcceptButtonText(text[, who])
    wrapper HeroSelector.setFrameText

function HeroSelector.enablePick(flag[, who])
    enable/disable the accept/random button also makes them visible for that players and hides the ban Button.
    
function HeroSelector.enableBan(flag[, who])
    enable/disable the ban button also makes accept/random invisible for that players and shows the ban Button.

function HeroSelector.forceRandom([who])
    wrapper for doRandom for player

function HeroSelector.forcePick([who])
    forces to pick what currently is selected, if that fails doRandom

function HeroSelector.buttonRequirementDone(unitCode, player)

function HeroSelector.deselectButtons([buttonIndex])
    deselect selected buttons for all players with 0 or nil
    when an index is given only this specific buttonIndex

function HeroSelector.update()
    reDo possible selection, textures and enability for all heroButtons.

function HeroSelector.getDisabledIcon(icon)
    ReplaceableTextures\CommandButtons\BTNHeroPaladin.tga -> ReplaceableTextures\CommandButtonsDisabled\DISBTNHeroPaladin.tga

function HeroSelector.showFrame(frame, flag[, who])
    Set the visibility of frame to flag when who includes the local player by the rules of function HeroSelector.includesPlayer

function HeroSelector.includesPlayer(who, player)
    does player include who?
    return true, if yes.
    return false otherwise
    who can be a number(GetPlayerTeam), a race(GetPlayerRace), a player, a force(BlzForceHasPlayer) or
    nil => true    

function HeroSelector.counterChangeUnitCode(unitCode, add, player)
    increases/decreases the counter for picks of unitCode for the player's team.
    This can allow/disallow picking this unit for that team.

function HeroSelector.rollOption(player, includeRandomOnly, excludedIndex, category)
    get an random Unitcode from the added options
    returns an unitcode or nil when none could be found
--]]


JASS:
library HeroSelector initializer init_function requires optional FrameLoader
//HeroSelector V1.7
//API
//=====
//HeroSelectorForcePick()
//HeroSelectorForcePickPlayer(player p)
//HeroSelectorForcePickRace(race r)
//HeroSelectorForcePickTeam(integer teamNr)

//HeroSelectorForceRandom()
//HeroSelectorForceRandomRace(race r)
//HeroSelectorForceRandomTeam(integer teamNr)

//HeroSelectorDoPick(player p)
//HeroSelectorDoRandom(player p)

//HeroSelectorShow(boolean flag)
//HeroSelectorShowForce(boolean flag, force f)
//HeroSelectorShowRace(boolean flag, race r)
//HeroSelectorShowPlayer(boolean flag, player p)
//HeroSelectorShowTeam(boolean flag, integer teamNr)

//HeroSelectorEnableBan(boolean flag)
//HeroSelectorEnableBanRace
//HeroSelectorEnableBanTeam
//HeroSelectorEnableBanPlayer
//HeroSelectorEnableBanForce

//HeroSelectorEnablePick(boolean flag)
//HeroSelectorEnablePickForce
//HeroSelectorEnablePickPlayer
//HeroSelectorEnablePickTeam
//HeroSelectorEnablePickRace

//HeroSelectorRollOption(player p, boolean includeRandomOnly, integer exculdedIndex, integer category) returns integer
//HeroSelectorCounterChangeUnitCode(integer unitCode, integer add, player p)
//HeroSelectorEnableButtonIndex(integer unitCode, integer buttonIndex)
//HeroSelectorDisableButtonIndex(integer buttonIndex, integer teamNr)
//HeroSelectorButtonRequirementDone(integer unitCode, player p) returns boolean
//HeroSelectorDeselectButton(integer buttonIndex)

//HeroSelectorAddUnit(integer unitCode, boolean onlyRandom)
//HeroSelectorAddUnitCategory(integer unitCode, integer category)
//HeroSelectorSetUnitCategory(integer unitCode, integer category)
//HeroSelectorSetUnitReqPlayer(integer unitCode, player p)
//HeroSelectorSetUnitReqRace(integer unitCode, race r)
//HeroSelectorSetUnitReqForce(integer unitCode, force f)
//HeroSelectorSetUnitReqTeam(integer unitCode, integer teamNr)
//HeroSelectorSetUnitReqTechLevel(integer unitCode, integer techCode, integer techLevel)

//HeroSelectorSetFrameText(framehandle frame, string text)
//HeroSelectorSetFrameTextPlayer
//HeroSelectorSetFrameTextForce
//HeroSelectorSetFrameTextTeam
//HeroSelectorSetFrameTextRace

//HeroSelectorSetTitleText(string text)
//HeroSelectorSetTitleTextRace
//HeroSelectorSetTitleTextPlayer
//HeroSelectorSetTitleTextForce
//HeroSelectorSetTitleTextTeam

//HeroSelectorSetBanButtonText
//HeroSelectorSetBanButtonTextPlayer
//HeroSelectorSetBanButtonTextRace
//HeroSelectorSetBanButtonTextForce
//HeroSelectorSetBanButtonTextTeam

//HeroSelectorSetRandomButtonText
//HeroSelectorSetRandomButtonTextPlayer
//HeroSelectorSetRandomButtonTextForce
//HeroSelectorSetRandomButtonTextTeam
//HeroSelectorSetRandomButtonTextRace

//HeroSelectorSetAcceptButtonText
//HeroSelectorSetAcceptButtonTextPlayer
//HeroSelectorSetAcceptButtonTextForce
//HeroSelectorSetAcceptButtonTextTeam
//HeroSelectorSetAcceptButtonTextRace

//HeroSelectorAddCategory(string icon, string text) //should only be used before the category Buttons are created

//HeroSelectorGetDisabledIcon(string iconPath)

//HeroSelectorUpdate()

//=====

    
    globals
        //Setup
        private string TocPath            = "war3mapImported\\HeroSelector.toc" //ex/import also "HeroSelector.fdf"
        //Box
        private string BoxFrameName            = "HeroSelectorRaceBox" //this is the background box being created
        private real BoxPosX                   = 0.3
        private real BoxPosY                   = 0.4
        private framepointtype BoxPosPoint     = FRAMEPOINT_CENTER
        private boolean AutoShow               = true //(true) shows the box and the Selection at 0.0 for all players
        //Unique Picks
        public integer UnitCount              = 2 //each hero is in total allowed to be picked this amount of times (includes random, repicking allows a hero again).
        public integer UnitCountPerTeam       = 1 //Each Team is allowed to pick this amount of each unitType
        private string  ToManyTooltip          = "OUTOFSTOCKTOOLTIP"
        //Ban
        private boolean DelayBanUntilPick      = false //(true) baning will not be applied instantly, instead it is applied when HeroSelectorEnablePick is called the next time.
        //Category
        private boolean CategoryAffectRandom   = true  //(false) random will not care about selected category
        private boolean CategoryMultiSelect    = false //(false) deselect other category when selecting one, (true) can selected multiple categories and all heroes having any of them are not filtered.
        private real CategorySize              = 0.02  //the size of the Category Button
        private real CategorySpaceX            = 0.0008 //space between 2 category Buttons, it is meant to need only one line of Categoryy Buttons.
        private integer CategoryFilteredAlpha  = 45     // Alpha value of Heroes being filtered by unselected categories
        private boolean CategoryAutoDetectHero = true // Will create and remove added Heroes to read and setup the Category for the primary Attribute Str(4) Agi(8) Int(16)
            //Icon path, tooltip Text (tries to localize)
        //Indicator
        private framehandle IndicatorSelected
        private framehandle IndicatorSelectedParent
        private string IndicatorPathPick       = "UI\\Feedback\\Autocast\\UI-ModalButtonOn.mdl" //this model is used by the indicator during picking
        private string IndicatorPathBan        = "war3mapImported\\HeroSelectorBan.mdl" //this model is used by the indicator during baning
        //Grid
        private real SpaceBetweenX             = 0.008 //space between 2 buttons in one row
        private real SpaceBetweenY             = 0.008 //space between 2 rows
        private integer ButtonColCount         = 4 //amount of buttons in one row
        private integer ButtonRowCount         = 4 //amount of rows
        
        private boolean ChainedButtons         = true //(true) connect to the previous button/ or row, (false) have a offset to the box topLeft in this moving a button has no effect on other buttons.
        //Button
        private real ButtonSize                = 0.036 //size of each button
        private boolean ButtonBlendAll         = false //(true) when a hero icon uses transparenzy
        private string EmptyButtonPath         = "UI\\Widgets\\EscMenu\\Human\\blank-background.blp"
        private boolean HideEmptyButtons       = true
        //Ban Button
        private string BanButtonTextPrefix     = "|cffcf2084" //Prefix Text for the Ban Button
        private string BanButtonText           = "CHAT_ACTION_BAN" //tries to get a Localized String
        private real BanButtonSizeX            = 0.085
        private real BanButtonSizeY            = 0.03
        private string BanTooltip              = "DISALLOWED"
        private boolean BanIgnoreRequirment    = true // (true) Ban is not restricted by Requirments
        //BanDone Button
        private string BanDoneButtonTextPrefix    = "|cffcf2084" //Prefix Text for the Ban Button
        private string BanDoneButtonText          = "QUESTACCEPT" //tries to get a Localized String
        private real BanDoneButtonSizeX         = 0.085
        private real BanDoneButtonSizeY         = 0.03
        public boolean BanDoneButtonIsShown       = false //show this button in the Ban Phase
        //Accept Button
        private string AcceptButtonTextPrefix       = ""
        private string AcceptButtonText             = "ACCEPT"
        private real AcceptButtonSizeX              = 0.085
        private real AcceptButtonSizeY              = 0.03
        public boolean AcceptButtonIsShown         = true
        //Random Button
        private string RandomButtonTextPrefix       = ""
        private string RandomButtonText             = "RANDOM" //tries Localizing
        private real RandomButtonSizeX              = 0.085
        private real RandomButtonSizeY              = 0.03
        private boolean RandomButtonIsShown         = true
        private boolean RandomButtonPick            = false //(true) pressing the random button will pick the option. (false) pressing the random button will select a button, random only heroes can not be selected, but that does not matter. This weak random and randomonly should not be combined.
        //Repick Button
        private string RepickButtonTextPrefix = ""
        private string RepickButtonText       = "REPICK" //tries Localizing
        private real RepickButtonSizeX      = 0.085
        private real RepickButtonSizeY      = 0.03
        public boolean RepickButtonIsShown    = true //show this button in the Pick Phase
        //Tooltip
        private string TooltipPrefix                  = "|cffffcc00"
        private real TooltipOffsetX                   = 0
        private real TooltipOffsetY                   = 0
        private framepointtype TooltipPoint           = FRAMEPOINT_BOTTOM //pos the Tooltip with which Point
        private framepointtype TooltipRelativePoint   = FRAMEPOINT_TOP //pos the Tooltip to which Point of the Relative
        private boolean TooltipRelativIsBox           = false          //(true) use the box as anchor, (false) use the button as anchor
        private string TooltipRequires                = "QUESTCOMPONENTS"

        //System variables, Do not touch
        public integer HeroButtonCount        = ButtonRowCount*ButtonColCount
        public unit array PickedUnit
        private trigger CategoryClickTrigger   = CreateTrigger()
        private trigger AcceptButtonTrigger  = CreateTrigger()
        private trigger BanButtonTrigger      = CreateTrigger()
        private trigger RandomButtonTrigger  = CreateTrigger()
        private trigger RepickButtonTrigger  = CreateTrigger()
        private trigger BanDoneButtonTrigger  = CreateTrigger()
        
        private framehandle BanButton
        private framehandle AcceptButton
        private framehandle RandomButton
        private framehandle BanDoneButton
        private framehandle RepickButton
        private player array DelayBanPlayer
        private integer array DelayBanUnitCode
        private integer DelayBanCount = 0
        public framehandle array CategoryButton
        public framehandle array CategoryIconFrame
        public framehandle array CategoryIconPushedFrame        
        public framehandle array CategoryTooltipFrame
        public framehandle array CategoryTooltipFrameBox
        public string array CategoryText
        public string array CategoryTexture
        public string array CategoryTextureDisabled
        private integer array CategoryButtonValue
        public integer CategoryButtonCount = 0
        private integer array UsedTeamNr
        private integer UsedTeamNrCount = 0

        private integer ButtonHeroCount = 0        
        private integer array ButtonHeroUnitCode


        private integer array HeroTotalCount
        public integer array HeroCategory
        private integer array HeroRegType
        private player array HeroRegPlayer
        private force array HeroRegForce
        private integer array HeroRegNumber
        private integer array HeroRegNumber2
        private race array HeroRegRace

        private integer HeroCount = 0
        private integer array HeroUnitCode
        private integer array HeroButtonIndex


        public hashtable Hash = InitHashtable()
        framehandle HeroSelectorBox
        private framehandle HeroSelectorBoxSeperator
        private framehandle HeroSelectorBoxTitle

        private integer array HeroButtonUnitCode
        private integer HeroButtonUnitCodeCount = 0
        private framehandle array HeroButtonIcon
        private framehandle array HeroButtonIconPushed        
        private framehandle array HeroButtonIconDisabled
        private framehandle array HeroButtonTooltip
        private framehandle array HeroButtonTooltipBox
        private framehandle array HeroButtonFrame
        private trigger HeroButtonClickTrigger = CreateTrigger()

        private integer array PlayerSelectedButtonIndex
        private integer array PlayerSelectedCategory
        private integer array PlayerLastSelectedCategoryIndex

        private integer LastAction = 0
    endglobals

    private function AutoDetectCategory takes integer unitCode returns integer
        local integer value = 0
        local unit u
        local integer primaryAttribute
        if IsUnitIdType(unitCode, UNIT_TYPE_MELEE_ATTACKER) then
            set value = 1            
        elseif IsUnitIdType(unitCode, UNIT_TYPE_RANGED_ATTACKER) then
            set value = 2
        endif
        if CategoryAutoDetectHero and IsUnitIdType(unitCode, UNIT_TYPE_HERO) then
            set u = CreateUnit(Player(bj_PLAYER_NEUTRAL_EXTRA), unitCode, 0, 0, 270)
            set primaryAttribute = BlzGetUnitIntegerField(u, UNIT_IF_PRIMARY_ATTRIBUTE)
            call RemoveUnit(u)
            set u = null

            if ConvertHeroAttribute(primaryAttribute) == HERO_ATTRIBUTE_STR then
                set value = value + 4
            elseif ConvertHeroAttribute(primaryAttribute) == HERO_ATTRIBUTE_AGI then
                set value = value + 8
            elseif ConvertHeroAttribute(primaryAttribute) == HERO_ATTRIBUTE_INT then 
                set value = value + 16
            endif
        endif
        return value
    endfunction

    private function GetBorderSize takes nothing returns real
        if GetPlayerRace(GetLocalPlayer()) == RACE_HUMAN then
            return 0.029
        elseif GetPlayerRace(GetLocalPlayer()) == RACE_ORC then
            return 0.029
        elseif GetPlayerRace(GetLocalPlayer()) == RACE_UNDEAD then
            return 0.035
        elseif GetPlayerRace(GetLocalPlayer()) == RACE_NIGHTELF then
            return 0.035
        elseif GetPlayerRace(GetLocalPlayer()) == RACE_DEMON then
            return 0.024
        else 
            return 0.0
        endif
    endfunction



  • HeroSelectorGUI Plus
    • Events
      • Map initialization
    • Conditions
    • Actions
      • -------- Random Only Hero do not take slots --------
      • Set HeroSelectorRandomOnly[1] = Klingenmeister
      • Set HeroSelectorRandomOnly[2] = Tauren-Häuptling
      • Set HeroSelectorRandomOnly[3] = Gruftlord
      • Set HeroSelectorRandomOnly[4] = Wächterin
      • -------- Positions of the UnitCodes taking buttons. --------
      • -------- Can only use upto HeroSelector.ButtonColCount*HeroSelector.ButtonRowCount fields --------
      • -------- The first button start at the top left with index 1. --------
      • -------- Unset fields are autofilled with empty slots. --------
      • Set HeroSelectorUnitCode[2] = Paladin
      • Set HeroSelectorUnitCode[3] = Erzmagier
      • Set HeroSelectorUnitCode[5] = Bergkönig
      • Set HeroSelectorUnitCode[6] = Blutmagier
      • Set HeroSelectorUnitCode[7] = Scharfseher
      • Set HeroSelectorUnitCode[8] = Schattenjäger
      • Set HeroSelectorUnitCode[9] = Dämonenjäger
      • Set HeroSelectorUnitCode[10] = Hüter des Hains
      • Set HeroSelectorUnitCode[11] = Mond-Priesterin
      • Set HeroSelectorUnitCode[12] = Todesritter
      • Set HeroSelectorUnitCode[14] = Lich
      • Set HeroSelectorUnitCode[15] = Schreckenslord
  • Start
    • Events
      • Time - Elapsed game time is 0.10 seconds
    • Conditions
    • Actions
      • Player Group - Pick every player in (All players) and do (Actions)
        • Loop - Actions
          • Unit - Create 1 Circle der Macht for (Picked player) at ((Picked player) start location) facing Default building facing degrees
      • -------- When the autoShowing is off, the HeroSelector has to be shown first --------
      • Custom script: HeroSelector.show(true)
      • -------- Hide picking buttons and show the bann button --------
      • Custom script: HeroSelector.enableBan(true)
  • BaningPhase
    • Events
      • Time - Every 0.75 seconds of game time
    • Conditions
    • Actions
      • -------- Runs 3 times in 4 seconds changes the Title. --------
      • -------- Show Remaining Counts in the Titel --------
      • Set Count = (31 - (Execution count of (This trigger)))
      • Custom script: HeroSelector.setTitleText( "Baning: " .. udg_Count)
      • -------- Time Run up? --------
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • Count Less than or equal to 0
        • Then - Actions
          • -------- Enable Picking --------
          • Custom script: HeroSelector.enablePick(true)
          • -------- Swap Active Trigger (Phase) --------
          • Trigger - Turn off (This trigger)
          • Trigger - Turn on Picking Phase <gen>
          • -------- Update Titel Right Now --------
          • Custom script: HeroSelector.setTitleText( "Picking: ")
        • Else - Actions
  • Picking Phase
    • Events
      • Time - Every 0.75 seconds of game time
    • Conditions
    • Actions
      • -------- Runs 3 times in 4 seconds changes the Title. --------
      • Set Count = (31 - (Execution count of (This trigger)))
      • Custom script: HeroSelector.setTitleText( "Picking: " .. udg_Count)
      • -------- The Time run up? --------
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • Count Less than or equal to 0
        • Then - Actions
          • -------- Pick everyone --------
          • Player Group - Pick every player in (All players) and do (Actions)
            • Loop - Actions
              • Custom script: bj_wantDestroyGroup = true
              • -------- This one has a hero? --------
              • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
                • If - Conditions
                  • (Random unit from (Units owned by (Picked player) matching (((Matching unit) is A Hero) Equal to True))) Equal to No unit
                • Then - Actions
                  • -------- No, force a Pick for him --------
                  • Custom script: HeroSelector.forcePick(GetEnumPlayer())
                • Else - Actions
          • Trigger - Turn off (This trigger)
          • Custom script: HeroSelector.destroy()
        • Else - Actions


Credits (jass):
Nestharus/Bribe

changelog:
1.8b Lua)
Better support for DisabledIcons
Max Repicks Config
AutoCreation on/off
Improved Category Setup
Do/ForceRandom improved
1.8a) Teamviewer Bugfix, Added ShowButton
1.8)
added requirement playerstate
Improved Req Tooltip
Added config Options
1.7)
Add Repick Button
Add Teamviewer.Reset()
Add Teamviewer swap Feature
Add BanDoneButton
TocPath in the setup section.

1.6a) compatible with TasFrameLoader
1.6)
Added TeamViewer Scale
HeroInfo can display Skills.
(Lua)
Various Bugfixes
function HeroSelector.unitCreated takes now the unitCode as arg instead and has to create the unit.
Can now Prevent options from being Randomed (HeroSelector.UnitDataPool[unitCode] = false)
added function HeroSelector.clearUnitData() to clear current UnitData making it easy to fill it with new data.
Added a GUI Real-Variable-Event for the default unitPicked function in which last created unit is the picked unit
1.5c)
Lua: Fixed a nil Pointer when categories were added after the box was already created: using function HeroSelector.addCategory(icon, text)​
1.5b)
Changed Fdf
Added visual Feedback while pushing Hero/Category Buttons
Bann/Random/Accept Button play a clicked sound.[/INDENT]

1.5)
Improved Teamviewer and added a non Teambased one
Improved Tooltips
Disabled Options show the reason (out of stock / not allowed / requirment)
HeroSelector box border became smaller and a seperator was added
Title Text is limited to the HeroSelector box
Banning can now ignore Requirments (on default enable)
The vJass version allowed to select Heroes which were above the Team limit after HeroSelectorUpdate was called[/INDENT]
Requires a new fdf[/INDENT]
1.4)
Supports TechCode Level as req
GUI can setup manual category mods.
Skips example code in InitHeroes right after the GUI Loader[/INDENT]
1.3b) Various fixes for the demo map Triggers
1.3a) Teamviewer showed non-Allies, when one didn't wanted that.
1.3) Autodetects Primary Attribute of heroes, can be disabled
1.2.1 only vJass) Seperated the Action functions from the main Code, the functions are now evoked by a real Event System.
1.2)
RandomButton can now select, if wanted.
Added Category
Increased BorderSize HUMAN, ORC
Some Bugfixes
TeamViewer the TextFrame Button posing was replaced with a Left2Right boolean[/INDENT]
1.1)
Heroes can now have requirements
Replaced how Random works internally.
Added Settings for Random and Accept Button
The HeroButtons can now be created without being bound together.
Renamed: enablePicking, DelayBannUntilPicking
Teamviewer and HeroInfo overlap now HeroSelector.destroy
bug fixes.​
Contents

HeroSelector (Map)

HeroSelector Jass (Map)

Level 12
Joined
May 16, 2020
Messages
660
Thanks a lot! After playing around with the values it improved a lot:

WC3-Scrn-Shot-092620-195852-001.png


But still two problems:

First, I tried to use the values from above for HeroInfo (TOPRIGHT + BOTTOMRIGHT), but it's not in the "middle + bottom" as I want. Instead, it snaps to the bottom right corner of the HeroSelector window. And if the HeroInfo windows is wide enough, it goes out on the left. Thus I tried the following:

private framepointtype TextAreaPoint = FRAMEPOINT_CENTER
private framepointtype TextAreaRelativePoint = FRAMEPOINT_BOTTOM

... which however doesn't snap precisely to the end of the HeroSelector window. Any way to fix this?

Secondly, a small aestetic issue: The indent before the "Team 2" name is smaller than before the "Team 1" name. Fixable?

***

Edit: Btw, an idea which could fit to your testmap: If you add the BattleNetTick to the banning and picking phase after X seconds every second, it makes it quite more noticable when you have little time left to choose :)

  • BaningPhase
    • Events
      • Time - Every 1.00 seconds of game time
    • Conditions
    • Actions
      • -------- Show Remaining Counts in the Titel --------
      • Set VariableSet HeroSelector_Count = (21 - (Execution count of (This trigger)))
      • Custom script: call HeroSelectorSetTitleText( "Banning: " + I2S(udg_HeroSelector_Count))
      • -------- 5 seconds left --------
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • HeroSelector_Count Less than or equal to 5
          • HeroSelector_Count Greater than or equal to 1
        • Then - Actions
          • Sound - Play BattleNetTick <gen>
        • Else - Actions
      • -------- Time Run up? --------
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • HeroSelector_Count Less than or equal to 0
        • Then - Actions
          • Sound - Play InviteToGame <gen>
          • -------- Enable Picking --------
          • Custom script: call HeroSelectorEnablePick(true)
          • -------- Swap Active Trigger (Phase) --------
          • Trigger - Turn off (This trigger)
          • Trigger - Turn on Picking Phase <gen>
          • -------- Update Titel Right Now --------
          • Custom script: call HeroSelectorUpdate()
          • Custom script: call HeroSelectorSetTitleText( "Picking: ")
        • Else - Actions
 
If you want the Infobox below Heroselector you should use one of these for the Infobox
FRAMEPOINT_TOP
FRAMEPOINT_TOPLEFT
FRAMEPOINT_TOPRIGHT
and one of these for the RelativePoint
FRAMEPOINT_BOTTOMLEFT
FRAMEPOINT_BOTTOM
FRAMEPOINT_BOTTOMRIGHT

Secondly, a small aestetic issue: The indent before the "Team 2" name is smaller than before the "Team 1" name. Fixable?
It is. Inside function TeamViewerInit when the code checks for left2Right one needs to change the text alignment of the textFrame in the else path (right side).

call BlzFrameSetTextAlignment(playerFaceName[playerIndex], TEXT_JUSTIFY_TOP, TEXT_JUSTIFY_RIGHT)
BlzFrameSetTextAlignment(textFrame, TEXT_JUSTIFY_TOP, TEXT_JUSTIFY_RIGHT)

Edit: Btw, an idea which could fit to your testmap: If you add the BattleNetTick to the banning and picking phase after X seconds every second, it makes it quite more noticable when you have little time left to choose
Good idea
 
Level 12
Joined
May 16, 2020
Messages
660
TOP + BOTTOM did the trick!

It is. Inside function TeamViewerInit when the code checks for left2Right one needs to change the text alignment of the textFrame in the else path (right side).

call BlzFrameSetTextAlignment(playerFaceName[playerIndex], TEXT_JUSTIFY_TOP, TEXT_JUSTIFY_RIGHT)
BlzFrameSetTextAlignment(textFrame, TEXT_JUSTIFY_TOP, TEXT_JUSTIFY_RIGHT)

Mm I see 3 different places where it checks for left2Right. Which one is the right one to replace the text? (or do you mean add?)

JASS:
    if left2Right then
        call BlzFrameSetPoint(movingFrame, FRAMEPOINT_TOPLEFT, relativFrame, FRAMEPOINT_BOTTOMLEFT, 0, 0)
    else
        call BlzFrameSetPoint(movingFrame, FRAMEPOINT_TOPRIGHT, relativFrame, FRAMEPOINT_BOTTOMRIGHT, 0, 0)
    endif
JASS:
    if left2Right then
        call BlzFrameSetPoint(movingFrame, FRAMEPOINT_TOPLEFT, relativFrame, FRAMEPOINT_TOPRIGHT, CategoryButtonGap, 0)
    else
        call BlzFrameSetPoint(movingFrame, FRAMEPOINT_TOPRIGHT, relativFrame, FRAMEPOINT_TOPLEFT, -CategoryButtonGap, 0)
    endif
JASS:
            if left2Right then
               call BlzFrameSetPoint(playerFaceTooltip[playerIndex], FRAMEPOINT_BOTTOMLEFT, playerFaceButton[playerIndex], FRAMEPOINT_TOPLEFT, 0, 0)
               call BlzFrameSetPoint(playerParentFrame[playerIndex], FRAMEPOINT_TOPLEFT, playerFaceButton[playerIndex], FRAMEPOINT_TOPLEFT, -0.007, 0.007)
            else
               call BlzFrameSetPoint(playerFaceTooltip[playerIndex], FRAMEPOINT_BOTTOMRIGHT, playerFaceButton[playerIndex], FRAMEPOINT_TOPRIGHT, 0, 0)
               call BlzFrameSetPoint(playerParentFrame[playerIndex], FRAMEPOINT_TOPRIGHT, playerFaceButton[playerIndex], FRAMEPOINT_TOPRIGHT, 0.007, 0.007)
            endif


****

And sorry some further questions (I keep finding some things which I want to change...):

  1. Currently, Player 1 and 7 (= PC players) select a hero as soon as the picking phase is over. How do I prevent this?
  2. Typing in -ar is not really selecting heroes "randomly" - it's usually the same 2 hero combination picked, i.e. X and Y or Z and W, but never X and Z etc.
  3. In the HeroInfo window: Is it possible to have the window start at the top of the text, not at the very bottom? Otherwise you always have to scroll up to the beginning of the text when selecting a hero. This is only a issue for when the text in HeroInfo is quite large, but I assume others will want to do the same.

https://i.ibb.co/7KytgzC/WC3-Scrn-Shot-092920-093357-001.png
 
Last edited:
JASS:
function TeamViewerInit takes nothing returns nothing
....
...
            set LastTeamFaceButton[teamNr] = playerFaceButton[playerIndex]
            call PosFrame(playerFaceName[playerIndex], playerFaceButton[playerIndex], left2Right)
            if left2Right then
               call BlzFrameSetPoint(playerFaceTooltip[playerIndex], FRAMEPOINT_BOTTOMLEFT, playerFaceButton[playerIndex], FRAMEPOINT_TOPLEFT, 0, 0)
               call BlzFrameSetPoint(playerParentFrame[playerIndex], FRAMEPOINT_TOPLEFT, playerFaceButton[playerIndex], FRAMEPOINT_TOPLEFT, -0.007, 0.007)
            else
               call BlzFrameSetPoint(playerFaceTooltip[playerIndex], FRAMEPOINT_BOTTOMRIGHT, playerFaceButton[playerIndex], FRAMEPOINT_TOPRIGHT, 0, 0)
               call BlzFrameSetPoint(playerParentFrame[playerIndex], FRAMEPOINT_TOPRIGHT, playerFaceButton[playerIndex], FRAMEPOINT_TOPRIGHT, 0.007, 0.007)
               call BlzFrameSetTextAlignment(playerFaceName[playerIndex], TEXT_JUSTIFY_TOP, TEXT_JUSTIFY_RIGHT)
            endif

Typing in -ar is not really selecting heroes "randomly" - it's usually the same 2 hero combination picked, i.e. X and Y or Z and W, but never X and Z etc.
Random is broken in any Map testing in V1.32.8. The patchnotes for the next version, Warcraft 3 V1.32.9 PTR, mention a fix.
Warcraft III: Reforged PTR Patch Notes - Version 1.32.9
  • Disabling Use Fixed Random Seed now properly randomizes the seed.
Currently, Player 1 and 7 (= PC players) select a hero as soon as the picking phase is over. How do I prevent this?
The Demo GUI trigger forces any Player to pick something when the Picking Phase is over, when that player did not already pick a hero.
You have to exclude Player 1 and 7 from that.
  • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
    • If - Conditions
      • (Picked player) Not equal to Player 1 (Red)
      • (Picked player) Not equal to Player 7 (Green)
      • (Random unit from (Units owned by (Picked player) matching (((Matching unit) is A Hero) Equal to Wahr).)) Equal to No unit
    • Then - Actions
      • -------- No, force a Pick for him --------
      • Custom script: HeroSelector.forcePick(GetEnumPlayer())
    • Else - Actions
In the HeroInfo window: Is it possible to have the window start at the top of the text, not at the very bottom? Otherwise you always have to scroll up to the beginning of the text when selecting a hero. This is only a issue for when the text in HeroInfo is quite large, but I assume others will want to do the same.
No Idea.
 
Last edited:
Level 12
Joined
May 16, 2020
Messages
660
Thank you tons - all solved! :)

Edit: Something which I think is an error within the test map and might help others: It contains a set bj_wantDestroyGroup = true, which I think shouldn't be there. Otherwise some random other group you might have at the time of picking ending gets destroyed. Was trying to find a weird bug yesterday and pinned it down to this one line.

  • Picking Phase
    • Events
      • Time - Every 0.75 seconds of game time
    • Conditions
    • Actions
      • -------- Runs 3 times in 4 seconds changes the Title. --------
      • Set VariableSet Count = (31 - (Execution count of (This trigger)))
      • Custom script: call HeroSelectorSetTitleText( GetLocalizedString("DEFAULTTIMERDIALOGTEXT")+": " +I2S( udg_Count))
      • -------- The Time run up? --------
      • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
        • If - Conditions
          • Count Less than or equal to 0
        • Then - Actions
          • -------- Pick everyone --------
          • Player Group - Pick every player in (All players) and do (Actions)
            • Loop - Actions
              • Custom script: set bj_wantDestroyGroup = true
              • -------- This one has a hero? --------
              • If (All Conditions are True) then do (Then Actions) else do (Else Actions)
                • If - Conditions
                  • (Random unit from (Units owned by (Picked player) matching (((Matching unit) is A Hero) Equal to True).)) Equal to No unit
                • Then - Actions
                  • -------- No, force a Pick for him --------
                  • Custom script: call HeroSelectorForcePickPlayer(GetEnumPlayer())
                • Else - Actions
          • Trigger - Turn off (This trigger)
          • Trigger - Turn off Repick <gen>
          • Custom script: call HeroSelectorDestroy()
          • Custom script: call TeamViewerDestroy()
          • Custom script: call HeroInfoDestroy()
        • Else - Actions
 
Last edited:
Normaly the button becomes gray after picking/banning. This is done in HeroSelectorAction. There is a function running when an unit is created/picked and one for banning process. Inside them use one of these
call HeroSelectorEnablePickPlayer(false, p)
call HeroSelectorEnableBanPlayer(false, p)

function HeroSelectorUnitCreated
call HeroSelectorEnablePickPlayer(false, p)
function HeroSelectorUnitBaned
call HeroSelectorEnableBanPlayer(false, p)​
 
Level 12
Joined
May 16, 2020
Messages
660
When I set call HeroSelectorEnableBanPlayer(true, p) I can ban an unlimited amount of units. Not sure if that's intended?

But I think the problem is more a Text Color issue. Since yes, with "false" the Buttin is grayed out and not clickable after banning once. But I want to remove the pink color once I banned something.
 
Last edited:
When I set call HeroSelectorEnableBanPlayer(true, p) I can ban an unlimited amount of units. Not sure if that's intended?
It is intended. With that call you allow player p to ban. When you would use false instead of true the player could not ban anymore.

I checked the System's functions again and there is a function to set the text of the Button for specific players only. You could use it after disabling the Ban for that Player.
call HeroSelectorSetBanButtonTextPlayer("text", p)
call HeroSelectorSetAcceptButtonTextPlayer("text", p)
call HeroSelectorSetRandomButtonTextPlayer("text", p)
 
Level 12
Joined
May 16, 2020
Messages
660
Sorry didn't notice you responded already!

I checked the System's functions again and there is a function to set the text of the Button for specific players only. You could use it after disabling the Ban for that Player.

But changing the text to for example from "ban" --> "banned" for Player p after banning is still inferior in my opinion to recoloring the text it from pink --> grey (and it's also inconsistent with how it works for picking).

Isn't there the option to "just" change the pink color of the text after banning? (same as with picking)
 
The colored text on the Buttons is applied using the warcraft 3 color codes as prefix for the Text.
If you use a different color prefix when setting the text you have them in different color.
From the system
JASS:
call BlzFrameSetText(BanButton, BanButtonTextPrefix + GetLocalizedString(BanButtonText))
call BlzFrameSetText(AcceptButton, AcceptButtonTextPrefix + GetLocalizedString(AcceptButtonText))
call BlzFrameSetText(RandomButton, RandomButtonTextPrefix + GetLocalizedString(RandomButtonText))
You can't use this lines because this variables are all private.

Hence I suggested this ButtonText setters.
 
Level 12
Joined
May 16, 2020
Messages
660
Ohh, now I understood what you mean. This solves the issue:
JASS:
    //happens when the banButton is pressed, player is the one having pressed the button
    function HeroSelectorUnitBaned takes nothing returns nothing
        local player p = udg_HeroSelectorEventPlayer
        local integer playerIndex = GetPlayerId(p)
        local integer unitCode = udg_HeroSelectorEventUnitCode
        call HeroSelectorEnableBanPlayer(false, p)        //only one ban
    call HeroSelectorSetBanButtonTextPlayer("Ban", p)    //added this line

        set p = null
      
    endfunction
 
Level 2
Joined
Oct 29, 2020
Messages
14
I love this menu! but I am kind of newbie with configuring this, I was able to get it ported into my map and it works very nicely!!

I use vJass, and I have a few questions:

- can I have the menu wait 20-30seconds before appearing? I have a couple other "mode dialogues" that run at the start for players to choose different settings, and I need to give those time to run before the actual game starts

- can I choose to have the menu only appear for specific players? I do not need this menu for every player in my map (thats actually the most important question I need solved)

- the sound "BattlenetTick" doesnt seem to load, not sure what Im missing?

- Can i change the words "Ban" to "Veto"? and "Disallow" To "Vetoed" or Blocked (sorry if I sound nitpicky)

- does Banning a hero effect all players, like if p2 bans farseer nobody can pick farseer? Im wondering if it can be changed to only effect opponents?

- what is the point of clicking on a players hero icon in the top left corner? why does it display a msg saying "player clicked on player"? does that serve any purpose? can I change what message is displayed?

- did you also add AI so computer controller players will use the menu? that is awesome!!

- I really like the way you added the melee, ranged, strength, etc, to highlight different heroes! very cool :) maybe you can also add icons for each race too?

thanks!
 
Last edited:
yes, you can change HeroSelector to not show itself with it's creation and show it later for players you want. But you also have to change the demo Triggers in case you use them.

First you call HeroSelectorInit() at 0.0s or later and change the config value from HeroSelector globals
private boolean AutoShow = true
->
private boolean AutoShow = false

Then after the 20-30 seconds expired you use one of these, depends who you wana show it to.
call HeroSelectorShow(true)

call HeroSelectorShowPlayer(true, player p)

call HeroSelectorShowForce(true, force f)
call HeroSelectorShowRace(true, race r)
call HeroSelectorShowTeam(true, integer teamNr)

If you use the GUI Demos Picking/Banning then you would have to turn both off and when you show Heroselector enable the wanted Phase.

- Can i change the words "Ban" to "Veto"? and "Disallow" To "Vetoed" or Blocked (sorry if I sound nitpicky)
yes, in the globals block of HeroSelector search for string variables and change the text.
for example:
private string BanButtonText = "CHAT_ACTION_BAN"
->
private string BanButtonText = "Veto"

- does Banning a hero effect all players, like if p2 bans farseer nobody can pick farseer? Im wondering if it can be changed to only effect opponents?
all Players are affected by Ban. You would have to change the system code if you want only a team to be affected. (requires some (v)jass skill)

- what is the point of clicking on a players hero icon in the top left corner? why does it display a msg saying "player clicked on player"? does that serve any purpose? can I change what message is displayed?
HeroSelector itself does nothing with it. One could add some functionality for that in Teamviewer - private function PlayerFaceClicked.
Some Mobas allow Swaping Heroes with that one could create such quite simple, if one can write (v)jass.

- did you also add AI so computer controller players will use the menu? that is awesome!!
One can force Players to do a selection which is used onto Bots, in the demo Map.
call HeroSelectorForcePick()
call HeroSelectorForcePickPlayer(player p)
call HeroSelectorForcePickRace(race r)
call HeroSelectorForcePickTeam(integer teamNr)

call HeroSelectorForceRandom()
call HeroSelectorForceRandomRace(race r)
call HeroSelectorForceRandomTeam(integer teamNr)

call HeroSelectorDoPick(player p)
call HeroSelectorDoRandom(player p)

- I really like the way you added the melee, ranged, strength, etc, to highlight different heroes! very cool :) maybe you can also add icons for each race too?
One can add other Categories, if wanted. it is some work though one has to define new categories with text, icon and a way to detect the category onto unitTypes.
Inside HeroSelectorAction public function InitHeroes the default categories are set this adds the Button, one could additional change the autodetection from HeroSelector private function AutoDetectCategory to get the categories for added Options correctly. Or you manualy set the categories inside that InitHeroes function.

- the sound "BattlenetTick" doesnt seem to load, not sure what Im missing?
Did you add it in Sound Editor?
 
Level 2
Joined
Oct 29, 2020
Messages
14
woo wow awesome! thank you so much :) this is a lot of work for me so I will have to take a look at it later and I will let you know how it goes, thanks again!

Then after the 20-30 seconds expired you use one of these, depends who you wana show it to.
call HeroSelectorShow(true)

call HeroSelectorShowPlayer(true, player p)

would you mind explaining this one a little more please? Im a newb with vJass and only really know GUI :(

what I want is for the hero selection menu to only show up for players 2, 3, 5, 6, 8, 9, 11, and 12... but not for players 1, 4, 7, 10, 13, 14, or higher

also... how can I change the location that the hero spawns at? can I set it to be a building instead of the players start location?
 
Last edited:
also... how can I change the location that the hero spawns at? can I set it to be a building instead of the players start location?
One can change the spawn location inside HeroSelectorAction - function HeroSelectorUnitCreated.
One Replaces
call SetUnitPosition(u, GetPlayerStartLocationX(p), GetPlayerStartLocationY(p))
->
call SetUnitPosition(u, GetUnitX(udg_whatEver), GetUnitY(udg_whatEver))


you have to use use custom script or a moded GUI. To be able to use hand written custom functions.

If you have a fixed Team Game and all these players start in one Team then I suggest call HeroSelectorShowTeam(true, 0 or 1) (Team 1 in the Lobby is number 0 while Team 2 would be number 1).

If they all wanted players are already gathered in a Playergroup the call HeroSelectorShowForce(true, udg_Force)
if all the wanted players are Human Race call HeroSelectorShowRace(true, RACE_HUMAN).

Otherwise you need to show it for this Players by hand:
call HeroSelectorShowPlayer(true, ConvertedPlayer(2))
call HeroSelectorShowPlayer(true, ConvertedPlayer(3))
call HeroSelectorShowPlayer(true, ConvertedPlayer(5))
 
Level 2
Joined
Oct 29, 2020
Messages
14
I really do love this system! Its by far the best hero menu on HWS and Im sure many people will enjoy using it :) I have a few more suggestions, obviously I am way too novice to create any of this stuff myself, but if you are interested in adding some more features it would be really cool!

Currently the system has 2 timers: 24seconds to ban a hero and 24seconds to choose a hero. if the player does not click any heroes after 48seconds, then the system automatically gives the player a random hero. When the player selects to "ban" a hero the 1st timer continues counting down, but once the 1st countdown reaches 0 then players can immediately train a hero before the 2nd timer reaches 0... thats your current system which works just fine, but some players can start playing before others

Obviously I understand that I can increase the timers, but I dont want players stuck at the "ban" screen for too long... what I would prefer is if a player selects their hero quickly (before the 2nd timer expires) the hero enters a "queue" and waits for the timer to end (with their selected hero being the only one displaying in the menu). This way players can cancel or repick their choice while the menu/timer is still open and counting down, and by not spawning the hero right away it prevents them from starting play too early making for a more even playing field

Maybe there could even be an optional 3rd timer - so it would go 24sec for banning a hero, then 24seconds to select hero before hero is actually trained, and the final 24sec countdown would be for anybody who didnt select a hero (or is still deciding) before forcing them to take a random hero. This would also give players additional time to repick (using Esc) if they feel the need to, and it would just help players not feel rushed imo... I hope this makes sense lol

Lastly, if player 1 chooses All random (-ar) the menu instantly closes ignoring all timers. It would be nice if the menu stayed open and still allowed players to select a hero for banning (or do bans not effect random heroes?) ...and when players are waiting for the 2nd timer, you can keep the menu up to display only the players randomly chosen hero, maybe with a 1-time chance to repick a different random hero? before finally training the random hero after the 2nd timer expires

Once again, this all helps prevent players from starting early and keeps all players on the same starting point, which is especially helpful since my map only uses this menu for certain players and I need it to match standard melee times where heroes require 1-2minutes of training

I understand if some of these ideas are a little too complicated, and I know that I personally will never be able to make them function properly, but hopefully someday when you have the time you might consider adding these in, along with making some configurations and settings a little easier for newbs like me :)

great work, thanks again <3
 
Last edited:
Level 2
Joined
Oct 29, 2020
Messages
14
So after testing this for a while I am experiencing desyncs, it seems to occur even when using the original map without making any changes to it. Everything works fine in single player, but if you click the menu buttons (main menu, allies, etc) sometimes it can cause other players to randomly drop from the game :(

i think it also causes lag or slowdown throughout the game (even when there are no desyncs)
 
Last edited:
@Tasyen I noticed this line giving me trouble in game. Looks like buttonIndex isn't declared in this function. Not sure what that's supposed to be.

local tooltip = BlzCreateFrame("HeroSelectorText", box, 0, buttonIndex)
That is a copy paste error. The buttonIndex should be 0. Well It seems like I never tested to add categories after the box was already created :| , in the Lua version and it now produces a nil/null Pointer.
Should be fixed now with the new 1.5c Lua version.
 
Level 12
Joined
May 16, 2020
Messages
660
Hi Tasyen, I'm having troubles with the ban function and custom images.

The default reforged hero-icons grey out when you ban them. However, the custom import images stay the same color (not disabled) when banned. See picture below; The Shaman hero was banned, but the image it still "BTN" (not DISBTN"):

WC3ScrnShot_042421_162904_001.png


For reference, here the path this particular hero uses:
  • war3mapImported\BTNChampionShaman.dds
  • ReplaceableTextures\CommandButtonsDisabled\DISBTNChampionShaman.dds


Do you have any idea why this happens?
 
My disabled Texture detection is quite simple and works only when your image uses the default path for commandButtons.
ReplaceableTextures\CommandButtons\whatever

JASS:
function HeroSelectorGetDisabledIcon takes string icon returns string
        //ReplaceableTextures\CommandButtons\BTNHeroPaladin.tga -> ReplaceableTextures\CommandButtonsDisabled\DISBTNHeroPaladin.tga
        if SubString(icon, 34, 35) != "\\" then
            return icon
        endif //this string has not enough chars return it
        //string.len(icon) < 34 then return icon end //this string has not enough chars return it
        return SubString(icon, 0, 34) + "Disabled\\DIS" + SubString(icon, 35, StringLength(icon))
    endfunction
 
Level 12
Joined
May 16, 2020
Messages
660
Is there an Event function which I can use in GUI to recognize when a player selected a hero?

I want to adjust my Multiboard with the picked hero icon after a player selected a hero, and need to "grab" this information somehow.

Most likely this is the correct place in JASS, but I don't know how to access it with GUI:

JASS:
    function HeroSelectorUnitCreated takes nothing returns nothing
        local player p = udg_HeroSelectorEventPlayer
        local integer playerIndex = GetPlayerId(p)
        local unit u = udg_HeroSelectorEventUnit
        local boolean isRandom = udg_HeroSelectorEventIsRandom
        set bj_lastCreatedUnit = u
        if isRandom then
            //randomed
        else
            //picked
        endif

        call SetUnitPosition(u, GetPlayerStartLocationX(p), GetPlayerStartLocationY(p))

        if p == Player(1) then
                   
        endif
        call TeamViewerUnitCreated(p, u, isRandom)
        call PanCameraToTimedForPlayer(p, GetUnitX(u), GetUnitY(u),0)
        call SelectUnitForPlayerSingle(u, p)
        call HeroSelectorEnablePickPlayer(false, p) //only one pick for this player

        set p = null
        set u = null
    endfunction
 
The jass version has a real variable event which you can use in GUI. The Lua version does not.

Event variables:
udg_HeroSelectorEvent
udg_HeroSelectorEventUnit
udg_HeroSelectorEventUnitCode
udg_HeroSelectorEventPlayer

udg_HeroSelectorEvent is either 1.0, 2.0 or 3.0
1.0 means a hero is created.
2.0 a hero Button in the UI is clicked
3.0 when a hero is Baned.

BlzGetAbilityIcon(udg_HeroSelectorEventUnitCode) gives you the Icon of the Hero in the Events.
 
The shown GIF uses either an advanced Tooltip for each Button which is a Box (BACKDROP) or a FRAME with a Box. The Box has children one is a Frame of type TEXT and another FRAME of Type SPRITE. Or it uses empty Tooltips and moves around a Box.

A Frame of Type SPRITE can display (animated) warcraft 3 models.
There is a Tutorial about SPRITE-Frame in the graveyard it shows how to display an unitModel onto the screen using Frames: UI: Adding Sprite
 
Level 20
Joined
Jul 10, 2009
Messages
477
Hey Tasyen,

thanks a lot for providing this resource! Will definitely use it in my current project :)

I'd also like to give you a bit of feedback about the things I found confusing upon implementing the Lua version of your system:

  1. Certain callbacks like HeroSelector.unitCreated and HeroSelector.buttonSelected are described at the top of your documentation, but it wasn't clear to me, how to use them. I first thought these functions were intended to be hooks in the style of onAcceptButtonClick(callback), but that didn't quite fit into their description. Then I tried to define the mentioned function HeroSelector.unitCreated(player,unit,isRandom) in my own code, but noticed this was corrupting the TeamViewer and HeroInfo functionality, apparently because those systems also use buttonSelected in their code, which I had accidently overwritten. Eventually I found that this function was already present in the HeroSelector code and that I could edit it there directly without producing further bugs. Even then, I had to read through the existing lines in the function to understand, which parts were necessary for functionality and which parts were just dummy code serving as explanation to the mapper (like the player == Player(1) line).
    IMO there is a bit of room for improvement here: You could mention in the docs that we have to edit the function that already exists in the HeroSelector code, but honestly, users should not have to read through your code in order to use your system correctly. Why don't you give us hooks that we can call from anywhere? After editing the internals of your system, updating to a newer version would be rather hard (we would just overwrite all settings and callbacks) and it leads to spaghetti code in my map (because the things I wanted to do on button selection are coded elsewhere). Proper hooks would solve that issue and even allow TeamViewer and HeroInfo to use them and stay separate.
  2. You could add more explanation to the documentation. Several functions have no explanation at all and after the issue stated in 1) above, I am rather unsure on which functions I can use "from outside" and which ones I have to edit in your code.
  3. Clicking on "Accept" immediately creates a unit and the HeroSelector.unitCreated callback takes exactly this unit as an argument. In my case, I didn't need any unit, but was rather interested in the unitCode belonging to the button clicked (to register it to my data structure and be able to create a unit later). So I had to workaround it by saving the created units unitCode and removing the unit. Works, but wouldn't it be smarter, if you provided a HeroSelector.heroPicked(player, unitCode, isRandom) taking an unitCode argument (or preferably onAcceptButtonClick(function)) callback instead and let the mapper decide, when to create a unit?
  4. In the Hero Selector box, hovering over non-added hero slots shows a corrupted tooltip. I can provide a screenshot, if you need it. Would it be suitable to deactivate non-used buttons?
  5. TeamViewer boxes are really large and don't really fit on screen for larger groups of players. Maybe provide a smaller variant? I know, I can change all sizes in the constants on top of the script, but its hard to judge, what sizes I need to modify, when I have no clue about UI design. Maybe have a real variable TeamViewer.scale = 1. that people could use to scale the whole thing?
  6. You have a two references to __jarray(0) in your code. Not sure, where that is declared (maybe a Lua or Wc3 thing?), but it's empty, when I look into it ingame. What is the purpose of this?
  7. The HeroSelector.forceRandom function has a line GetPlayerSlotState(player) == PLAYER_SLOT_STATE_PLAYING, but the player variable is not delared.

Again, thank you for the hard work you have put into this, much appreciated!

Best regards,
Eikonium
 
Thanks for your feedback.

You could add more explanation to the documentation. Several functions have no explanation at all and after the issue stated in 1) above, I am rather unsure on which functions I can use "from outside" and which ones I have to edit in your code.
I think it was intended that you only change functions upto the comment: code start, includes all this "hook" functions. I kinda wanted an unorganized hooking.

In the Hero Selector box, hovering over non-added hero slots shows a corrupted tooltip. I can provide a screenshot, if you need it. Would it be suitable to deactivate non-used buttons?
I know of that, did not see it as such a big deal. but it is quite easy to fix.

You have a two references to __jarray(0) in your code. Not sure, where that is declared (maybe a Lua or Wc3 thing?), but it's empty, when I look into it ingame. What is the purpose of this?
__jarray(0) is a warcraft 3 table creator which sets a default return value, in this case 0, for keys not set. Probably works based on metatable _index. I think it was created to retain the behaviour of jass arrarys.

Clicking on "Accept" immediately creates a unit and the HeroSelector.unitCreated callback takes exactly this unit as an argument. In my case, I didn't need any unit, but was rather interested in the unitCode belonging to the button clicked (to register it to my data structure and be able to create a unit later). So I had to workaround it by saving the created units unitCode and removing the unit. Works, but wouldn't it be smarter, if you provided a HeroSelector.heroPicked(player, unitCode, isRandom) taking an unitCode argument (or preferably onAcceptButtonClick(function)) callback instead and let the mapper decide, when to create a unit?
Yes, it would be smarter. But I think I wanted to make sure the unit exists for the limited pick counter (although it is not required). I think it is also a leftover of my earlier approach in which I did the random with Unitpool which allways creates an unit so all of them had to create an unit.

TeamViewer boxes are really large and don't really fit on screen for larger groups of players. Maybe provide a smaller variant? I know, I can change all sizes in the constants on top of the script, but its hard to judge, what sizes I need to modify, when I have no clue about UI design. Maybe have a real variable TeamViewer.scale = 1. that people could use to scale the whole thing?
good, will add such a feature.
  1. The HeroSelector.forceRandom function has a line GetPlayerSlotState(player) == PLAYER_SLOT_STATE_PLAYING, but the player variable is not delared.
Thanks for the catch.
 
Level 20
Joined
Jul 10, 2009
Messages
477
I think it was intended that you only change functions upto the comment: code start, includes all this "hook" functions. I kinda wanted an unorganized hooking.
There is a lot of code even before the code start line. HeroSelector.initHeroes() alone takes more than 100 lines of code and gives the reader the impression of not being in the customizable part anymore. Why would you require users to read past that function just to see HeroSelector.unitCreated?
Maybe you could put the functions intended for customization on top and point out in the documentation, what parts the user is intended to change?
Several functions stated in the comments block (like function HeroSelector.rollOption(player, includeRandomOnly, exculdedIndex, category)) could also deserve some more explanation :)

I know of that, did not see it as such a big deal. but it is quite easy to fix.
That would be great!

__jarray(0) is a warcraft 3 table creator which sets a default return value, in this case 0, for keys not set. Probably works based on metatable _index. I think it was created to retain the behaviour of jass arrarys.
Interesting, didn't know that, thanks! Just looked it up ingame. You are right about it being based on a metatable with a constant __index function.
 
Updated the Lua version to 1.6)
Fixed a nil Pointer in function HeroSelector.forceRandom
Not set HeroButtons are now hidden, disables the empty tooltipBox glitch (this feature can be disabled)
function HeroSelector.setFrameText did allways affect HeroSelector.Title
Added TeamViewer.Scale, it scales the PlayerFrames.
Fixed a icon glitch while clicking the hero inside Teamviewer's playerinfos
HeroInfo displays now upto 7 (default) skills the selected has. But one nees to feed HeroInfo with the skillList oneself (unit skills can be autodected).
function HeroSelector.unitCreated takes now the unitCode as arg instead and has to create the unit.
function HeroSelector.rollOption reuses a table.
FRAMEEVENT_CONTROL_CLICK are now handleded by TasButtonAction. Reduces the amount of triggers for Frame click events to 1.
Removed a possible Desync Reason, (created a Trigger in the Lua root).
heroButton.IconPushed did wrongly refere to the same Frame as heroButton.Icon
The init function does now use HeroSelector.update() instead of setting it oneself.
Can now Prevent options from being Randomed (HeroSelector.UnitDataPool[unitCode] = false)
added function HeroSelector.clearUnitData() to clear current UnitData making it easy to fill it with new data.
Added a GUI Real-Variable-Event for the default unitPicked function in which last created unit is the picked unit.
Removed HeroSelector.disableButtonIndex & HeroSelector.enableButtonIndex from the API list. They are not meant to be used outside.
 
Just out of curiosity: Under which circumstances can trigger creation in the Lua root cause desyncs?
I assume, it is safe as long as you don't add events, right?
I came to the conclusion it is safer to not create any warcraft 3 object inside the Lua root.
Here why:

The Lua root happens twice, once when the map is opened in the lobby and once when the game is started. One can test this by writing a simple PreloadGen in the Lua root.
Code:
PreloadGenStart()
Preload("Test")
PreloadGenEnd("LuaRoot.txt")
Enter the lobby check the timestamp of the Generated file (Documents\Warcraft III\CustomMapData) wait until your os's timestamp is distinctable and run the map. The timestamp will change, means all code in the Lua root runs twice.

When one creates a simple timer in the Lua root which every second prints to the screen at some point it will stop or when one has a trigger with an event. After some time the trigger will stop.
My guess is that garbage collector runs and finds them without any reference so it kills them (Trigger, TriggerEvent, TriggerAction, TriggerCondition).
Although Lua root runs twice as proven with the PreloadGen we only have one print onto the screen, which means warcraft 3 somehow found this lobby objects and stoped them or they never could do their thing.

Alone the first is a problem, it creates quite some garbage with code in the Lua root and most Warcraft 3 objects have to be in sync, triggerActions of triggers with different handledIds running will desync or units... .

I am unsure how this produces desyncs but it sometimes does. Maybe because the handleIds of these "lobby garbage" is not recycled in the same order. Because of this assumation I once started to just have a reference to every warcraft 3 object created in the root which kept the Trigger doing its job but now I say just play safe and create warcraft 3 objects as direct or indirect result of the execution of function main (Map init).
 
Level 20
Joined
Jul 10, 2009
Messages
477
Thanks a lot for this information!

By "Lua root", you refer to all things written in "free code", i.e. everything not being part of any function, right?

Interestingly, Global Initilization should be unaffected by that bug. One might think after reading your statement that writing onInitialization(func) in the lua root would queue func twice, but it doesn't, because even the table saving the queue structure would be created again.
 
By "Lua root", you refer to all things written in "free code", i.e. everything not being part of any function, right?
Yeah, code free outside of any function.

Interestingly, Global Initilization should be unaffected by that bug. One might think after reading your statement that writing onInitialization(func) in the lua root would queue func twice, but it doesn't, because even the table saving the queue structure would be created again.
true. so there is another reason for it?


Updated the vjass version to 1.6)
HeroInfo displays now upto 7 (default) skills the selected has. But one nees to feed HeroInfo with the skillList oneself (unit skills can be auto-found)
Not set HeroButtons are now hidden, disables the empty tooltipBox glitch (this feature can be disabled)
init function does now use HeroSelectorUpdate() instead of setting it oneself.
added (TeamViewer) Scale, it scales the PlayerFrames.

The jass version now uses [Snippet] Ascii from Nestharus/Bribe.
 
Level 20
Joined
Jul 10, 2009
Messages
477
true. so there is another reason for it?
The fact that onInitialization is only queueing a function once is perfectly in line with the bug you suggested. That hint of mine was rather a side note on the topic.

Order is (assuming you are right):
1. The Global Initialization library creates its data structure (lets call it A)
2. any use of onInitialization queues a function call into A that waits to be executed at map init.
3. Lua root runs again, so Global Init library creates its data structure again, overwriting A by a new table B. A is not being referenced anymore.
4. any use of onInitialization queues a function call into B
5. all functions queued into B are executed at map init. A is not referenced anymore and thus not executed, no matter if its already garbage collected or not.
 
Level 5
Joined
Jun 11, 2014
Messages
58
  1. They use Requirments set with HeroSelectorSetUnitReqRace in HeroSelectorAction public function InitHeroes. In the Demo map Nightelf and Human Heroes can only be picked by Plaers of their Races
I'm having trouble figuring out how to set requirements for Heroes. When I play the demo map none of the heroes are grayed out for me, regardless of which race I choose, so it seems to not be working? Am I missing something?

I attached some screenshots where I selected Undead as race but can still choose a Night Elf (or any) Hero.
 

Attachments

  • WC3ScrnShot_090921_130940_001.png
    WC3ScrnShot_090921_130940_001.png
    2.1 MB · Views: 42
  • WC3ScrnShot_090921_130949_001.png
    WC3ScrnShot_090921_130949_001.png
    2.8 MB · Views: 45
I'm having trouble figuring out how to set requirements for Heroes. When I play the demo map none of the heroes are grayed out for me, regardless of which race I choose, so it seems to not be working? Am I missing something?

I attached some screenshots where I selected Undead as race but can still choose a Night Elf (or any) Hero.
For some Reason the demo code that setups the requirements is disabled. It is prevented by a return in HeroSelectorAction - public function InitHeroes. The req feauture itself is working.
 
Level 12
Joined
May 16, 2020
Messages
660
Hey Tasyen, I currently have around 70 heroes to pick from, and I want to optimize the map by only turning on triggers which belong to picked heroes.

So for example:

If a player picks HeroSelectorUnitCode[62]...
  • HeroSelectorGUI
    • Events
      • Map initialization
    • Conditions
    • Actions
      • -------- Random Only Hero do not take slots --------
      • Set VariableSet HeroSelectorRandomOnly[1] = Blademaster
      • Set VariableSet HeroSelectorRandomOnly[2] = Tauren Chieftain
      • Set VariableSet HeroSelectorRandomOnly[3] = Crypt Lord
      • Set VariableSet HeroSelectorRandomOnly[4] = Warden
      • -------- Positions of the UnitCodes taking buttons. --------
      • -------- Can only use upto HeroSelector.ButtonColCount*HeroSelector.ButtonRowCount fields --------
      • -------- The first button start at the top left with index 1. --------
      • -------- Unset fields are autofilled with empty slots. --------
      • -------- --------
      • -------- STRENGTH --------
      • Set VariableSet HeroSelectorUnitCode[1] = Earthshaker
      • Set VariableSet HeroSelectorUnitCode[2] = Stone Giant
      • Set VariableSet HeroSelectorUnitCode[3] = Beastmaster
      • Set VariableSet HeroSelectorUnitCode[4] = Centaur Warchief
      • Set VariableSet HeroSelectorUnitCode[13] = Dragon Knight
      • Set VariableSet HeroSelectorUnitCode[14] = Pandaren Brewmaster
      • Set VariableSet HeroSelectorUnitCode[15] = Rogue Knight
      • Set VariableSet HeroSelectorUnitCode[16] = Bristleback
      • Set VariableSet HeroSelectorUnitCode[25] = Tidehunter
      • Set VariableSet HeroSelectorUnitCode[26] = Spirit Breaker
      • Set VariableSet HeroSelectorUnitCode[27] = Chaos Knight
      • Set VariableSet HeroSelectorUnitCode[28] = Lord of Avernus
      • Set VariableSet HeroSelectorUnitCode[37] = Doom Bringer
      • Set VariableSet HeroSelectorUnitCode[38] = Lycanthrope
      • Set VariableSet HeroSelectorUnitCode[39] = Night Stalker
      • Set VariableSet HeroSelectorUnitCode[40] = Slithereen Guard
      • Set VariableSet HeroSelectorUnitCode[49] = Butcher
      • Set VariableSet HeroSelectorUnitCode[50] = Sand King
      • Set VariableSet HeroSelectorUnitCode[51] = Skeleton King
      • Set VariableSet HeroSelectorUnitCode[52] = Axe
      • Set VariableSet HeroSelectorUnitCode[61] = Magnataur
      • Set VariableSet HeroSelectorUnitCode[62] = Swamp Monster
      • Set VariableSet HeroSelectorCategory[1] = 4
      • Set VariableSet HeroSelectorCategory[2] = 4
      • Set VariableSet HeroSelectorCategory[3] = 4
      • Set VariableSet HeroSelectorCategory[4] = 4
      • Set VariableSet HeroSelectorCategory[13] = 4
      • Set VariableSet HeroSelectorCategory[14] = 4
      • Set VariableSet HeroSelectorCategory[15] = 4
      • Set VariableSet HeroSelectorCategory[16] = 4
      • Set VariableSet HeroSelectorCategory[25] = 4
      • Set VariableSet HeroSelectorCategory[26] = 4
      • Set VariableSet HeroSelectorCategory[27] = 4
      • Set VariableSet HeroSelectorCategory[28] = 4
      • Set VariableSet HeroSelectorCategory[37] = 4
      • Set VariableSet HeroSelectorCategory[38] = 4
      • Set VariableSet HeroSelectorCategory[39] = 4
      • Set VariableSet HeroSelectorCategory[40] = 4
      • Set VariableSet HeroSelectorCategory[49] = 4
      • Set VariableSet HeroSelectorCategory[50] = 4
      • Set VariableSet HeroSelectorCategory[51] = 4
      • Set VariableSet HeroSelectorCategory[52] = 4
      • Set VariableSet HeroSelectorCategory[61] = 4
      • Set VariableSet HeroSelectorCategory[62] = 4
      • -------- AGILITY --------
      • Set VariableSet HeroSelectorUnitCode[5] = Anti-Mage
      • Set VariableSet HeroSelectorUnitCode[6] = Drow Ranger
      • Set VariableSet HeroSelectorUnitCode[7] = Dwarven Sniper
      • Set VariableSet HeroSelectorUnitCode[8] = Juggernaut
      • Set VariableSet HeroSelectorUnitCode[17] = Moon Rider
      • Set VariableSet HeroSelectorUnitCode[18] = Naga Siren
      • Set VariableSet HeroSelectorUnitCode[19] = Princess of the Moon
      • Set VariableSet HeroSelectorUnitCode[20] = Shadow Shaman
      • Set VariableSet HeroSelectorUnitCode[29] = Terror of the Tides
      • Set VariableSet HeroSelectorUnitCode[30] = Ursa Warrior
      • Set VariableSet HeroSelectorUnitCode[31] = Vengeful Spirit
      • Set VariableSet HeroSelectorUnitCode[32] = Bloodseeker
      • Set VariableSet HeroSelectorUnitCode[41] = Bone Fletcher
      • Set VariableSet HeroSelectorUnitCode[42] = Broodmother
      • Set VariableSet HeroSelectorUnitCode[43] = Gorgon
      • Set VariableSet HeroSelectorUnitCode[44] = Netherdrake
      • Set VariableSet HeroSelectorUnitCode[53] = Phantom Assassin
      • Set VariableSet HeroSelectorUnitCode[54] = Shadowfiend
      • Set VariableSet HeroSelectorUnitCode[55] = Soul Keeper
      • Set VariableSet HeroSelectorUnitCode[56] = Spectre
      • Set VariableSet HeroSelectorUnitCode[65] = Venomancer
      • Set VariableSet HeroSelectorUnitCode[66] = Faceless Void
      • Set VariableSet HeroSelectorUnitCode[67] = The Blade's Shadow
      • Set VariableSet HeroSelectorCategory[5] = 8
      • Set VariableSet HeroSelectorCategory[6] = 8
      • Set VariableSet HeroSelectorCategory[7] = 8
      • Set VariableSet HeroSelectorCategory[8] = 8
      • Set VariableSet HeroSelectorCategory[17] = 8
      • Set VariableSet HeroSelectorCategory[18] = 8
      • Set VariableSet HeroSelectorCategory[19] = 8
      • Set VariableSet HeroSelectorCategory[20] = 16
      • Set VariableSet HeroSelectorCategory[29] = 8
      • Set VariableSet HeroSelectorCategory[30] = 8
      • Set VariableSet HeroSelectorCategory[31] = 8
      • Set VariableSet HeroSelectorCategory[32] = 8
      • Set VariableSet HeroSelectorCategory[41] = 8
      • Set VariableSet HeroSelectorCategory[42] = 8
      • Set VariableSet HeroSelectorCategory[43] = 8
      • Set VariableSet HeroSelectorCategory[44] = 8
      • Set VariableSet HeroSelectorCategory[53] = 8
      • Set VariableSet HeroSelectorCategory[54] = 8
      • Set VariableSet HeroSelectorCategory[55] = 8
      • Set VariableSet HeroSelectorCategory[56] = 8
      • Set VariableSet HeroSelectorCategory[65] = 8
      • Set VariableSet HeroSelectorCategory[66] = 8
      • Set VariableSet HeroSelectorCategory[67] = 8
      • -------- INTELLIGENCE --------
      • Set VariableSet HeroSelectorUnitCode[9] = Crystal Maiden
      • Set VariableSet HeroSelectorUnitCode[10] = Elder Shaman
      • Set VariableSet HeroSelectorUnitCode[11] = Enchantress
      • Set VariableSet HeroSelectorUnitCode[12] = Lord of Olympia
      • Set VariableSet HeroSelectorUnitCode[21] = The Dreamer
      • Set VariableSet HeroSelectorUnitCode[22] = Ogre Magi
      • Set VariableSet HeroSelectorUnitCode[23] = Slayer
      • Set VariableSet HeroSelectorUnitCode[24] = Storm Spirit
      • Set VariableSet HeroSelectorUnitCode[33] = Tinker
      • Set VariableSet HeroSelectorUnitCode[34] = Twin Head Dragon
      • Set VariableSet HeroSelectorUnitCode[35] = Bane Elemental
      • Set VariableSet HeroSelectorUnitCode[36] = Death Prophet
      • Set VariableSet HeroSelectorUnitCode[45] = Demon Witch
      • Set VariableSet HeroSelectorUnitCode[46] = Enigma
      • Set VariableSet HeroSelectorUnitCode[47] = Lich
      • Set VariableSet HeroSelectorUnitCode[48] = Obsidian Destroyer
      • Set VariableSet HeroSelectorUnitCode[57] = Queen of Pain
      • Set VariableSet HeroSelectorUnitCode[58] = Tormented Soul
      • Set VariableSet HeroSelectorUnitCode[59] = Northwind
      • Set VariableSet HeroSelectorUnitCode[60] = Oblivion
      • Set VariableSet HeroSelectorUnitCode[63] = Silencer
      • Set VariableSet HeroSelectorUnitCode[64] = Windrunner
      • Set VariableSet HeroSelectorUnitCode[68] = Shadow Demon
      • Set VariableSet HeroSelectorCategory[9] = 16
      • Set VariableSet HeroSelectorCategory[10] = 16
      • Set VariableSet HeroSelectorCategory[11] = 16
      • Set VariableSet HeroSelectorCategory[12] = 16
      • Set VariableSet HeroSelectorCategory[21] = 16
      • Set VariableSet HeroSelectorCategory[22] = 16
      • Set VariableSet HeroSelectorCategory[23] = 16
      • Set VariableSet HeroSelectorCategory[24] = 16
      • Set VariableSet HeroSelectorCategory[33] = 16
      • Set VariableSet HeroSelectorCategory[34] = 16
      • Set VariableSet HeroSelectorCategory[35] = 16
      • Set VariableSet HeroSelectorCategory[36] = 16
      • Set VariableSet HeroSelectorCategory[45] = 16
      • Set VariableSet HeroSelectorCategory[46] = 16
      • Set VariableSet HeroSelectorCategory[47] = 16
      • Set VariableSet HeroSelectorCategory[48] = 16
      • Set VariableSet HeroSelectorCategory[57] = 16
      • Set VariableSet HeroSelectorCategory[58] = 16
      • Set VariableSet HeroSelectorCategory[59] = 16
      • Set VariableSet HeroSelectorCategory[60] = 16
      • Set VariableSet HeroSelectorCategory[63] = 16
      • Set VariableSet HeroSelectorCategory[64] = 16
      • Set VariableSet HeroSelectorCategory[68] = 16

...I want to enable all the associated triggers with that hero (marked yellow):
1643057917551.png

Would you recommend to do this in JASS in your system? Or can I somehow grab the information via GUI and work from there?
 
there is the real variable event udg_HeroSelectorEvent = 1.0 and the variable udg_HeroSelectorEventUnitCode you could create gui triggers with such event and a unitType check in which you then enable the triggers.
There is also the baned event udg_HeroSelectorEvent = 3.0
  • HeroSelector Paladin Picked
    • Events
      • Game - HeroSelectorEvent becomes Equal to 1.00
    • Conditions
      • HeroSelectorEventUnitCode Equal to Paladin
    • Actions
      • Game - Display to (All players) the text: Custom Paladin Pick...
 
Updated both to V1.6a)
Added TasFrameLoader and made HeroSelector work after Save&Load.

small changes were made to all UI-plugins

The init of HeroSelector does now call itself, before it was done from one of the GUI demo triggers.

Save&Load does not restore the current ban phase correctly... But it is load able now.

there are some problems with the Lua version stoping it from 1.6a...

Edit: The FrameLoader in Lua was wrong now updated both, vjass needs another update it does not restore the pick&ban enable yet
Edit: Updated vjass version to V1.6b) it restores pick&ban enable after Save&Load
 
Last edited:
Top