• Listen to a special audio message from Bill Roper to the Hive Workshop community (Bill is a former Vice President of Blizzard Entertainment, Producer, Designer, Musician, Voice Actor) 🔗Click here to hear his message!
  • Read Evilhog's interview with Gregory Alper, the original composer of the music for WarCraft: Orcs & Humans 🔗Click here to read the full interview.
  • The Hive's 22nd Icon Contest: Creep Abilities is now concluded, time to vote for your favourite set of icons! Click here to vote!
  • ✅ The POLL for Hive's Texturing Contest #34 is OPEN! Vote for the TOP 3 SKINS! 🔗Click here to cast your vote!
  • ✅ The POLL for Hive's Techtree Contest #20 is OPEN! Vote for the TOP 3 FACTIONS! 🔗Click here to cast your vote!

TasFlashyCommandButton

This bundle is marked as pending. It has not been reviewed by a staff member yet.
TasFlashyCommandButton is a custom UI system for Warcraft 3 V1.31 or higher.
It display additional autocast models ontop of command buttons while some script condition is true.

This works out of the box, no requirements needed. only put that code somewhere in you map inside trigger editor and request new sprites with TasFlashyCommandButton.Add.

Lua

vjass


Lua:
if Debug then Debug.beginFile "TasFlashyCommandButton" end
do 
--[[ TasFlashyCommandButtonV3a by Tasyen

A small system to display "autocast" sprites on commandbuttons while some wanted condition is fullfilled.
Only works correct in single unit selection, does not support spellbooks, nor built menues.
Abilities should have no colisions in command card.

TasFlashyCommandButton.Remove(who)
 removes and returns an activ entry (table)
 who can be an integer aka index or a table.

TasFlashyCommandButton(model, buttonIndex, name, condition, requiredSpell)
can be used to readd a removed entry in this case give the table instead of model.
 when requiredSpell provided, unit needs to have atleast level 1 to show it in the UI, can be either a FourCC or a FourCCString
 buttonIndex is the index of the command button (0-11). Can also be a spellCode or fourCC. Then it uses the button position of that spell.
 name is for you to access it by BlzGetFrameByName(name, 0)
 returns the new obj inside TasFlashyCommandButton
 will create warcraft 3 objects, do not use in Lua Root.
 checks all models on every timeout
 Does not work for observers
 
 condition can be a trigger, function, string or Lua table.
   trigger -> TriggerEvaluate use udg_TasFlashyCommandButtonUnit
   function(unit, sprite, data) return true to show it right now.
   string -> global array with that name has 1 or more for current Unit the limit 1 can be changed with array.TasFlashy = x
   table -> like string, but works less good with a table made with __jarray(0) and dont need to be a global table

 beaware, the condition is called in GetLocalPlayer context

   -- example show autocast model on blizzard while it is day
   TasFlashyCommandButton("UI/Feedback/Autocast/UI-ModalButtonOn.mdx", 'AHbz', "BlizzardDaySprite", 
      function(unit, sprite, data) return bj_dncIsDaytime end
   , 'AHbz')

   -- example show autocast model on attack while current order is attack
   OrderAttack = OrderId("attack")
   TasFlashyCommandButton("UI/Feedback/Autocast/UI-ModalButtonOn.mdx", 3, "AttackSprite", 
      function(unit, sprite, data) return GetUnitCurrentOrder(unit) == OrderAttack end
   , 'Aatk')


   TasFlashyCommandButton("UI/Feedback/Autocast/UI-ModalButtonOn.mdx", 3, "AttackSprite",  "udg_MyArray" , 'Aatk')
     show a sprite when global array with the name "udg_MyArray" has 1 or more for current Unit
     you can change the req value with udg_MyArray.TasFlashy = 5



     MyLuaArray = {}
     MyLuaArray = __jarray(0)
   TasFlashyCommandButton("UI/Feedback/Autocast/UI-ModalButtonOn.mdx", 3, "AttackSprite",  MyLuaArray , 'Aatk')
     MyLuaArray[udg_Unit] = 1
     MyLuaArray.TasFlashy = 5 -- default 1 but not with __jarray(0)

   TasFlashyCommandButton("UI/Feedback/Autocast/UI-ModalButtonOn.mdx", 3, "AttackSprite",  gg_trg_Trigger, 'Aatk')
      udg_TasFlashyCommandButtonUnit is the current Unit use the conditions when they return true it is okay.
]]
   local this ={
      data = {}
      ,unit = {}
   }
   setmetatable(this, {__call = function(table, model, buttonIndex, name, conditionFunc, spellCode) return this.Add(model, buttonIndex, name, conditionFunc, spellCode) end})
   local lastXSize = 0
   TasFlashyCommandButton = this

   local function CreateFrame(model, index, name)
      local sprite = BlzCreateFrameByType("SPRITE", name, this.Parent, "", 0)
      BlzFrameSetSize(sprite, 0.0001, 0.0001)
      BlzFrameSetModel(sprite, model, 0)
      BlzFrameSetVisible(sprite, false)
      BlzFrameSetPoint(sprite, FRAMEPOINT_BOTTOMLEFT, BlzGetOriginFrame(ORIGIN_FRAME_COMMAND_BUTTON, index), FRAMEPOINT_BOTTOMLEFT, 0, 0)
      return sprite
   end
   local function ReCreateFrames()
      -- reserve      
      for i = 0, 11 do BlzGetOriginFrame(ORIGIN_FRAME_COMMAND_BUTTON, i) end
      this.Parent = BlzGetFrameByName("ConsoleUIBackdrop", 0)
      -- (re)make custom frames
      this.Parent = BlzCreateFrameByType("FRAME", "", BlzGetOriginFrame(ORIGIN_FRAME_GAME_UI, 0), "", 0)
      lastXSize = 0
      for i, data in ipairs(this.data) do
         data[4] = CreateFrame(data[1], data[2], data[3])
      end
  end

  
   function this.Update()
      local unit = this.unit[GetLocalPlayer()]
      local show = true       
      if not unit then show = false end
      local hasControl = show and (IsUnitOwnedByPlayer(unit, GetLocalPlayer()) or GetPlayerAlliance(GetOwningPlayer(unit), GetLocalPlayer(), ALLIANCE_SHARED_CONTROL))
      if not hasControl then show = false end

      -- use hud UIscale
      if BlzFrameGetChild then -- when this native exists it could be reforged
         local newSize = BlzFrameGetWidth(BlzGetFrameByName("ConsoleUIBackdrop", 0))
         if newSize ~= lastXSize then
            lastXSize = newSize
            BlzFrameSetScale(this.Parent, newSize/0.8)
         end
      end

      for i, data in ipairs(this.data) do
         if show and (data[6] <= 0 or GetUnitAbilityLevel(unit, data[6]) > 0) then
            local frame = data[4]
            local filterData = data[5]
            local filterDataType = type(filterData)
            if filterDataType == "function" then
               BlzFrameSetVisible(frame, filterData(unit, frame, data))
            elseif filterDataType == "table" then
               BlzFrameSetVisible(frame, (filterData[unit] or 0) >= (filterData.TasFlashy or 1))
            elseif filterDataType == "string" then -- gui
               if _G[filterData] then BlzFrameSetVisible(frame, _G[filterData][unit] >= math.max(_G[filterData].TasFlashy, 1)) end
            elseif filterDataType == "userdata" then -- gui trigger
               udg_TasFlashyCommandButtonUnit = unit
               if filterData then BlzFrameSetVisible(frame, TriggerEvaluate(filterData)) end
            end
         else
            BlzFrameSetVisible(data[4], false)
         end
      end
      unit = nil
   end
   function this.Remove(who)
      local obj = nil
      if type(who) == "number" then
         obj = table.remove( this.data, who)
      else
         for i, data in ipairs(this.data) do if who == data then obj = table.remove(this.data, i) break end end
      end
      BlzFrameSetVisible(obj[4], false)
      return obj
   end
   function this.Add(model, buttonIndex, name, conditionFunc, spellCode)
      if not this.Timer then
         this.Timer = CreateTimer()
         TimerStart(this.Timer, 0.2, true, this.Update)
         local trig = CreateTrigger()
         TriggerAddAction(trig, function() this.unit[GetTriggerPlayer()] = GetTriggerUnit() end)
         TriggerRegisterAnyUnitEventBJ(trig, EVENT_PLAYER_UNIT_SELECTED)
         ReCreateFrames()
         if FrameLoaderAdd then FrameLoaderAdd(ReCreateFrames) end
      end
      if type(model) == "table" then
         table.insert(this.data, model)
         return model
      else
         if type(spellCode) == "string"  then spellCode = FourCC(spellCode) end
         if type(buttonIndex) == "string"  then buttonIndex = FourCC(buttonIndex) end
         if buttonIndex > 11 then buttonIndex = BlzGetAbilityPosX(spellCode)+ BlzGetAbilityPosY(spellCode)*4 end
         name = name or ""
         local newobj = {model, buttonIndex, name,  CreateFrame(model, buttonIndex, name), conditionFunc, spellCode or 0}
         table.insert(this.data, newobj)
         return newobj
      end
   end
end
if Debug then Debug.endFile() end

In the vjass version only function/trigger is supported as condition check.
JASS:
library TasFlashyCommandButton 
/**
 TasFlashyCommandButton2c by Tasyen

A small system to display "autocast" sprites on commandbuttons while some wanted condition is fullfilled.
Only works correct in single unit selection, does not support spellbooks, nor built menues.
Abilities should have no colisions in command card.

TasFlashyCommandButton_Add(string model, integer buttonIndex, string name, trigger conditionFunc, integer spellCode)
 when spellCode provided unit needs to have atleast level 1 to show it in the UI
 buttonIndex is the index of the command button (0-11). Can also be a spellCode. Then it uses the button position of that spell.
 name is for you to access it by BlzGetFrameByName(name, 0) 
 checks all models on every timeout
 Does not work for observers
 beaware, the function/trigger's Conditions is called in GetLocalPlayer context

TasFlashyCommandButton_AddEx(string model, integer buttonIndex, string name, code conditionFunc, integer spellCode)
    wrapper for TasFlashyCommandButton_Add, creates a trigger for you.

 */
globals
    // Where is the TOCFile in your map?
    public integer array DataSkill
    public trigger array DataTrigger 
    public framehandle array DataFrame
    public string array DataName
    public string array DataModel
    public integer array DataButtonIndex
    public integer DataCount = 0

    public framehandle Parent
    public timer Timer = null
    public trigger SelectTrigger
    public unit array Unit
    public real lastXSize = 0

endglobals

public function CreateFrame takes string model, integer index, string name returns framehandle
      local framehandle sprite = BlzCreateFrameByType("SPRITE", name, Parent, "", 0)
      call BlzFrameSetSize(sprite, 0.0001, 0.0001)
      call BlzFrameSetModel(sprite, model, 0)
      call BlzFrameSetVisible(sprite, false)
      call BlzFrameSetPoint(sprite, FRAMEPOINT_BOTTOMLEFT, BlzGetOriginFrame(ORIGIN_FRAME_COMMAND_BUTTON, index), FRAMEPOINT_BOTTOMLEFT, 0, 0)
      return sprite
   endfunction

   public function ReCreateFrames takes nothing returns nothing  
    local integer i  
      // reserve    
      set i = 0
      loop
        exitwhen i > 11
        call BlzGetOriginFrame(ORIGIN_FRAME_COMMAND_BUTTON, i)
        set i = i + 1
    endloop  
      set Parent = BlzGetFrameByName("ConsoleUIBackdrop", 0)
      // (re)make custom frames
      set Parent = BlzCreateFrameByType("FRAME", "", BlzGetOriginFrame(ORIGIN_FRAME_GAME_UI, 0), "", 0)
      set lastXSize = 0

      set i = DataCount
      loop
        exitwhen i <= 0
        set DataFrame[i] = CreateFrame(DataModel[i], DataButtonIndex[i], DataName[i])
        set i = i - 1
      endloop
  endfunction

  
  public function Update takes nothing returns nothing    
      local unit u = Unit[GetPlayerId(GetLocalPlayer())]
      local boolean show = true       
      local boolean hasControl = true       
      local real newSize

      local integer i
      if GetUnitTypeId(u) == 0 then 
        set show = false
      endif
      set hasControl = show and (IsUnitOwnedByPlayer(u, GetLocalPlayer()) or GetPlayerAlliance(GetOwningPlayer(u), GetLocalPlayer(), ALLIANCE_SHARED_CONTROL))
      if not hasControl then 
        set show = false
      endif

      // use hud UIscale
         set newSize = BlzFrameGetWidth(BlzGetFrameByName("ConsoleUIBackdrop", 0))
         if newSize != lastXSize then
            set lastXSize = newSize
            call BlzFrameSetScale(Parent, newSize/0.8)
         endif
         set i = 1
         loop
            exitwhen i > DataCount
            if show and GetUnitAbilityLevel(u, DataSkill[i]) > 0 then
                set udg_TasFlashyCommandButtonUnit = u
                call BlzFrameSetVisible(DataFrame[i], TriggerEvaluate(DataTrigger[i]))
            else
                call BlzFrameSetVisible(DataFrame[i], false)
            endif
            set i = i + 1
         endloop
      set u = null
   endfunction

   public function SelectAction takes nothing returns nothing    
        set Unit[GetPlayerId(GetTriggerPlayer())] = GetTriggerUnit()
   endfunction

   public function Add takes string model, integer buttonIndex, string name, trigger conditionFunc, integer spellCode returns nothing    
      if Timer == null then
         set Timer = CreateTimer()
         call TimerStart(Timer, 0.2, true, function Update)
         set SelectTrigger = CreateTrigger()
         call TriggerAddAction(SelectTrigger, function SelectAction )
         call TriggerRegisterAnyUnitEventBJ(SelectTrigger, EVENT_PLAYER_UNIT_SELECTED)
         call ReCreateFrames()
         static if LIBRARY_FrameLoader then
            call FrameLoaderAdd(function ReCreateFrames)
         endif
      endif
      if buttonIndex > 11 then 
        set buttonIndex = BlzGetAbilityPosX(spellCode)+ BlzGetAbilityPosY(spellCode)*4
      endif
      set DataCount = DataCount + 1
      set DataSkill[DataCount] = spellCode
      set DataTrigger[DataCount] = conditionFunc
      set DataFrame[DataCount] = CreateFrame(model, buttonIndex, name)
      set DataName[DataCount] = name
      set DataModel[DataCount] = model
      set DataButtonIndex[DataCount] = buttonIndex
   endfunction

   public function AddEx takes string model, integer buttonIndex, string name, code conditionFunc, integer spellCode returns nothing
    local trigger trig = CreateTrigger()
    call TriggerAddCondition(trig, Condition(conditionFunc))
    call Add(model, buttonIndex, name, trig, spellCode)
   endfunction

endlibrary

ChangeLog


V3 Lua) Remove & Readd entries
V2b api doc changes
V2 the conditionFunc gives the data returns the data on creation, can use a table, string or trigger instead of a conditionFunc​
Previews
Contents

TasFlashyCommandButton (Map)

TasFlashyCommandButton (Map)

Reviews
Antares
From the Lua submission rules: Indentation is 4 spaces and every block is required to be indented, with the rare exception of indenting the contents of an all-encompassing do-end-block because it's usually obvious then. Awaiting Update
Cool idea. Could not test it yet (since no test-map was provided :peasant-shocked:)

Noticed this:
Lua:
if type(data[5]) == "function" then

   BlzFrameSetVisible(data[4], data[5](unit, data[4], data))

elseif type(data[5]) == "table" then

   BlzFrameSetVisible(data[4], (data[5][unit] or 0) >= (data[5].TasFlashy or 1))

elseif type(data[5]) == "string" then -- gui

   if _G[data[5]] then BlzFrameSetVisible(data[4], _G[data[5]][unit] >= math.max(_G[data[5]].TasFlashy, 1)) end

elseif type(data[5]) == "userdata" then -- gui trigger

   udg_TasFlashyCommandButtonUnit = unit

   if data[5] then BlzFrameSetVisible(data[4], TriggerEvaluate(data[5])) end

end

Since this snippet is running inside an ipairs loop, which is inside an Update function (often called a lot), I think it would be good to sharpen it up a bit.

Perhaps something like:
Lua:
local data4 = data[4];
local data5 = data[5];
local data5Type = type(data5);
if data5Type == "function" then

   BlzFrameSetVisible(data4, data5(unit, data4, data))

elseif data5Type == "table" then

   BlzFrameSetVisible(data4, (data5[unit] or 0) >= (data5.TasFlashy or 1))

elseif data5Type == "string" then -- gui

   if _G[data5] then BlzFrameSetVisible(data4, _G[data5][unit] >= math.max(_G[data5].TasFlashy, 1)) end

elseif data5Type == "userdata" then -- gui trigger

   udg_TasFlashyCommandButtonUnit = unit

   if data5 then BlzFrameSetVisible(data4, TriggerEvaluate(data5)) end

end
This would save 1 table indexing operation at least regardless of the branching.
 
good idea, makes it more readable aswell.
Lua:
local frame = data[4]
local filterData = data[5]
local filterDataType = type(filterData)
if filterDataType == "function" then
   BlzFrameSetVisible(frame, filterData(unit, frame, data))
elseif filterDataType == "table" then
   BlzFrameSetVisible(frame, (filterData[unit] or 0) >= (filterData.TasFlashy or 1))
elseif filterDataType == "string" then -- gui
   if _G[filterData] then BlzFrameSetVisible(frame, _G[filterData][unit] >= math.max(_G[filterData].TasFlashy, 1)) end
elseif filterDataType == "userdata" then -- gui trigger
   udg_TasFlashyCommandButtonUnit = unit
   if filterData then BlzFrameSetVisible(frame, TriggerEvaluate(filterData)) end
end

Edit: Added demo map with 2 Lua examples and a gui condition.
Far seer summons 3 wolfs when 99% life or more
Paladin's next attack after using Holy LIght deals +60 spell damage.
 
Last edited:
Added a vjass version, it only supports function/trigger as condition. First wanted to also support some kind of data setup like in the Lua v2 but nah to much hassle.

Removed the download able Lua code, copy it from the website if you want or from within the demo map(s).
Awesome work!
 
I got quite confused with the variable, their names/functions/allowed types.

buttonIndex is called buttonIndex, but then you say it can be a "spellCode, or a FourCCString". Then you have another parameter called spellCode.
Suggestions:
-Say "buttonIndex is the index of the command button (0-11). Can also be a spellCode or fourCC. Then it uses the button position of that spell."
-Rename spellCode to "requiredSpell" and clarify that it can also be a spellCode or fourCC.

The function(unit, sprite, data) return true end does not say anything about what that function is supposed to represent. Call it condition in that parameter listing and then explain its purpose in the text below. "return true"? why not return boolean?

"beaware that the function/trigger's Conditions is called in GetLocalPlayer context"
that it is not called in GetLocalPlayer context, I assume?
"Do not call this function/trigger’s Conditions inside a GetLocalPlayer context."

The functionality of using strings or globals as the parameter #4 is again quite unclear and requires multiple reads since you unequivocally annotated it as a function at the top. Again, call it condition, then state what the allowed types are, then give examples below.

A lot of this would be greatly alleviated if you used function annotations
Lua:
---@param model string
---@param buttonIndex integer | string
---@param name string
---@param condition string | table | fun(whichUnit: unit, sprite: idk, data: idk)->boolean
---@param requiredSpell? integer | string
function this.Add(model, buttonIndex, name, conditionFunc, requiredSpell)

Indentation uses a weird 3-spaces-per-tab. What's going on there? Please fix. :sadpanda:

Don't see anything wrong with the code.
 
The function(unit, sprite, data) return true end does not say anything about what that function is supposed to represent. Call it condition in that parameter listing and then explain its purpose in the text below. "return true"? why not return boolean?
Left over of orginal version which only had function condition, i use this style so one can copy paste it.

"beaware that the function/trigger's Conditions is called in GetLocalPlayer context"
that it is not called in GetLocalPlayer context, I assume?
"Do not call this function/trigger’s Conditions inside a GetLocalPlayer context."
The function is called based on current displayed selection, it is like inside GetLocalPlayer context

A lot of this would be greatly alleviated if you used function annotations
no, ty.

The functionality of using strings or globals as the parameter #4 is again quite unclear and requires multiple reads since you unequivocally annotated it as a function at the top. Again, call it condition, then state what the allowed types are, then give examples below.
added infos from the examples to the function api doc
 
The function is called based on current displayed selection, it is like inside GetLocalPlayer context
Ah yea, my bad. That makes a lot more sense.

:pmeh:

A destroy method would again be nice to have. Someone might want to flash a button only for a few seconds, but then the logic would have to be checked for the rest of the game.

Indentation is sill messed up and weird.
 
Fine, will add a remove, but it mean also I need to implement a readd as I dont destroy frames.
Maybe i just check if the add function gives one table and that counts as readd.

Edit: updated Lua version to V3)
can now remove activ entries with TasFlashyCommandButton.Remove(who)
removed entries can be readded with TasFlashyCommandButton(objTable)

beaware that removed entries dont get repaired during Save & Load and readding a broken removed after save & load will crash the game.
 
Last edited:
Back
Top