Improved action bar plugin, with cooldowns

Posted by Nick Gammon on Mon 30 Mar 2009 08:26 PM — 32 posts, 173,990 views.

Australia Forum Administrator #0
Following on from this thread: http://www.gammon.com.au/forum/?id=9280 below is an improved version, which also shows cooldown times.

Many spells on a MUD have cooldowns (or recovery times). That is, if you cast (say) "fireball" you may not be allowed to cast it for another 10 seconds. Or some spells, like "recall" may not be re-usable for an hour.

This toolbar shows the time remaining by dimming the icon in a pie shape, with the dimmed amount gradually becoming less (like a clock face), until the time is up.

Optionally you can also show the actual time to go in text on top of the icon, so you can see the exact time (eg. "15" for 15 seconds, or "4m" for 4 minutes).

There is also an additional feature that you can play a sound file as a button is pressed, so you can select suitable sounds to be played as you cast spells.

Also, as a button is marked on cooldown, you are not allowed to press it again (you will hear a ding sound instead), so you don't use spells that are not cooled down.

To use all this, add extra lines to the configuration for each button (see examples in the plugin).

For example:


  cooldown = 10,          -- cooldown time in seconds
  sound = "chimes.wav",   -- sound to play when cast


Below is the plugin.

If you want the cooldowns shown as text set the show_time entity (line 4) to "y", otherwise "n".

Save between the lines as Icon_Bar.xml and use File menu -> Plugins to load that file as a plugin.


<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE muclient [
  <!ENTITY horizontal "y" > 
  <!ENTITY show_time "y" >
]>

<muclient>
<plugin
   name="Icon_Bar"
   author="Nick Gammon"
   id="ede0920fc1173d5a03140f0e"
   language="Lua"
   purpose="Shows a bar of buttons you can click on to do things"
   date_written="2009-02-26 09:00"
   date_modified="2010-05-31 17:22"
   requires="4.40"
   save_state="y"
   version="5.0"
   >
   
<description trim="y">
<![CDATA[
Install this plugin to show a button bar.

Click on an icon to execute a script or send a command to the MUD.

]]>
</description>

</plugin>

<!--  Timers  -->

<timers>
  <timer 
    script="handle_cooldowns" 
    enabled="y" second="1.00" 
    active_closed="y" >
  </timer>
</timers>


<!--  Script  -->

<script>

-- pull in entities outside the CDATA block

horizontal = ("&horizontal;"):lower ():sub (1, 1) == "y";
show_time = ("&show_time;"):lower ():sub (1, 1) == "y";


<![CDATA[

-- table of buttons

--[[
  Each button can have up to four entries:
  
  icon - filename of the image to draw
  tooltip - what to show if you hover the mouse over the button
  send - what to send to the MUD
  script - a script function to call
  cooldown - time spell takes to cool down, in seconds
  sound - sound to play when button pressed

--]]
  

buttons = {

  -- button 1
  {
  icon = "HealIcon.png",  -- icon image
  tooltip = "Heal self",  -- tooltip help text
  send = "cast 'lesser heal' self",  -- what to send
  cooldown = 10,          -- cooldown time in seconds
  sound = "chimes.wav",   -- sound to play when cast
  }, -- end of button 1
  
  -- button 2
  {
  icon = "SwordIcon.png",
  tooltip = "Attack",
  send = "kill @target",
  cooldown = 1 * 60 * 60,  -- 1 hour cooldown
  }, -- end of button 2
  
  -- button 3
 {
  icon = "SparkIcon2.png",
  tooltip = "Special Attacks",
  script = function ()   -- script to execute when clicked
                  
    -- choose from menu
    local result = WindowMenu (win, 
      WindowInfo (win, 14),  -- x
      WindowInfo (win, 15),   -- y
      "Backstab|Stun|Kick|Taunt")
    
    -- if not cancelled, do action on current target
    if result ~= "" then
      Send (result:lower () .. " " .. GetPluginVariable ("", "target"))
    end -- if

  
          end,  -- script function
            
    cooldown = 10 * 60,  -- 10 minute cooldown
  }, -- end of button 3
  
  
 --> add more buttons here
 
  
} -- end of buttons table


-- configuration

ICON_SIZE = 32

BACKGROUND_COLOUR = ColourNameToRGB "bisque"
BOX_COLOUR = ColourNameToRGB "royalblue"
BUTTON_EDGE = ColourNameToRGB "silver"

MOUSE_DOWN_COLOUR = ColourNameToRGB "darkorange"

-- where to put the window
WINDOW_POSITION = 6  -- top right
OFFSET = 6  -- gap inside box
EDGE_WIDTH = 2 -- size of border stroke

--[[
Useful positions:

4 = top left
5 = center left-right at top
6 = top right
7 = on right, center top-bottom
8 = on right, at bottom
9 = center left-right at bottom
--]]

-- font and size to use

FONT_NAME = "Lucida Sans Unicode"
FONT_SIZE = 18
FONT_SIZE_MEDIUM = 14
FONT_SIZE_SMALL = 10
FONT_COLOUR = ColourNameToRGB "yellow"

-- sound to play if on cooldown
ON_COOLDOWN_SOUND = "ding.wav"

frames = {}  -- remember where each icon was

require "commas"

function mousedown (flags, hotspot_id)

  if hotspot_id == "_" then
  
    -- find where mouse is so we can adjust window relative to mouse
    startx, starty = WindowInfo (win, 14), WindowInfo (win, 15)
    
    -- find where window is in case we drag it offscreen
    origx, origy = WindowInfo (win, 10), WindowInfo (win, 11)
  
    return
    end -- if
    
    
  local n = tonumber (hotspot_id)
  
  -- draw the button border in another colour for visual feedback
  WindowRectOp (win, 1, 
                frames [n].x1, frames [n].y1, frames [n].x2, frames [n].y2, 
                MOUSE_DOWN_COLOUR) 
  
  Redraw ()                
end -- mousedown

function cancelmousedown (flags, hotspot_id)
  local n = tonumber (hotspot_id)
  
  -- draw the button border in original colour for visual feedback
  WindowRectOp (win, 1, 
                frames [n].x1, frames [n].y1, frames [n].x2, frames [n].y2, 
                BUTTON_EDGE) 
  Redraw ()                

end -- cancelmousedown

function mouseup (flags, hotspot_id)
   
  -- fix border colour
  cancelmousedown (flags, hotspot_id)
  
  local n = tonumber (hotspot_id)
  
  local button = buttons [n]
  
  -- shift key clears cooldown
  if bit.band (flags, 1) == 1 then
    SetCooldown (n, nil)
    return
  end -- if
  
  -- can't press button if on cooldown
  if (button.cooldown_left or 0) > 0 then
    Sound (ON_COOLDOWN_SOUND)
    return
  end -- still on cooldown
  
  -- play sound if defined
  if button.sound then
    Sound (button.sound)
  end -- sound to play
  
  -- send to world if something specified
  if type (button.send) == "string" and 
    button.send ~= "" then
     
    local errors =  {} -- no errors yet
  
    -- function to do the replacements for string.gsub
    
    local function replace_variables (s)
      s = string.sub (s, 2)  -- remove the @
      replacement = GetPluginVariable ("", s)    -- look up variable in global variables
      if not replacement then  -- not there, add to errors list
        table.insert (errors, s)
        return
      end -- not there
      return replacement  -- return variable
    end -- replace_variables 
    
    -- replace all variables starting with @
    
    local command = string.gsub (button.send, "@%a[%w_]*", replace_variables)
    
    -- report any errors
    
    if #errors > 0 then
      for k, v in ipairs (errors) do
        ColourNote ("white", "red", "Variable '" .. v .. "' does not exist")
      end -- for
      return
    end -- error in replacing
     
    Execute (command)
  end -- if
  
  -- execute script if wanted
  if type (button.script) == "function" then
    button.script (n) 
  end -- if
     
  SetCooldown (n, button.cooldown)
  
end -- mouseup

function dragmove(flags, hotspot_id)

  -- find where it is now
  local posx, posy = WindowInfo (win, 17),
                     WindowInfo (win, 18)

  -- move the window to the new location
  WindowPosition(win, posx - startx, posy - starty, 0, 2);
  
  -- change the mouse cursor shape appropriately
  if posx < 0 or posx > GetInfo (281) or
     posy < 0 or posy > GetInfo (280) then
    SetCursor (11)   -- X cursor
  else
    SetCursor (10)   -- arrow (NS/EW) cursor
  end -- if
  
end -- dragmove

function dragrelease(flags, hotspot_id)
  local newx, newy = WindowInfo (win, 17), WindowInfo (win, 18)
  
  -- don't let them drag it out of view
  if newx < 0 or newx > GetInfo (281) or
     newy < 0 or newy > GetInfo (280) then
     -- put it back
    WindowPosition(win, origx, origy, 0, 2);
  end -- if out of bounds
  
end -- dragrelease

function SetCooldown (n, amount)
  assert (n >= 1 and n <= #buttons, 
          "Bad button number " .. n .. " to SetCooldown")
  if amount then
    assert (amount >= 0, "Bad amount " .. amount .. " to SetCooldown")
  end -- if
          
  local frame = frames [n] 
  local x1, y1, x2, y2 = frame.x1 + 1, frame.y1 + 1, frame.x2 - 1, frame.y2 - 1         
  buttons [n].cooldown_left = amount  -- cooldown time left in seconds
  local max = buttons [n].cooldown or 0   -- max cooldown time
  local percent  -- how far cooled down we are as a percent
  
  if max > 0 then
    percent = (amount or 0) / max 
  else
    percent = 0  -- don't divide by zero!
  end -- if
  
  -- reload the image
  if WindowDrawImage(win, n, 
                  x1, y1,   -- left, top
                  x2, y2,  -- right, bottom
                  2)  -- mode - stretch or shrink
    ~= error_code.eOK then
    WindowRectOp (win, 2, x1, y1,   -- left, top
                  x2, y2,  -- right, bottom
                  BACKGROUND_COLOUR)
  end
                 
  if amount and amount > 0 then
  
    -- calculate pie end point
    local endx = math.cos (math.rad (percent * 360 + 90)) * ICON_SIZE + ICON_SIZE / 2
    local endy = -1 * math.sin (math.rad (percent * 360 + 90)) * ICON_SIZE + ICON_SIZE / 2
    
    -- clear temporary window
    WindowRectOp (tempwin, 2, 0, 0, 0, 0, 0xFFFFFF)  -- fill with white
    
    -- draw the pie showing amount of cooldown
    WindowCircleOp (tempwin, 5, -10, -10, ICON_SIZE + 10, ICON_SIZE + 10,   -- pie
          0x000000, 5, 0,   -- no pen
          0x000000, 0,  -- solid brush, black
          ICON_SIZE / 2, 0,   -- from 12 o'clock position
          endx, endy)
    
    -- turn pie shape into an image
    WindowImageFromWindow(win, "mask", tempwin)
                      
    -- blend in (darken mode) with 50% opacity
    WindowBlendImage(win, "mask", 
                     x1, y1, x2, y2,  -- rectangle
                     5,  -- darken
                     0.5)  -- opacity
                  
    -- if they want to see the time left (text on top of the button) do that now   
    if show_time then
      local font = "f"
      local time_left = convert_time (amount)
      time_left = string.gsub (time_left, "[ s]", "") -- get rid of spaces, and "s"
      local time_len = WindowTextWidth (win, font, time_left)
      
      -- use smaller font if it doesn't fit
      if time_len > ICON_SIZE then
        font = "f2"
        time_len = WindowTextWidth (win, font, time_left)
  
        -- still too big?        
        if time_len > ICON_SIZE then
          font = "f3"
          time_len = WindowTextWidth (win, font, time_left)
        end -- if
     
      end -- if
      
      local font_height = WindowFontInfo (win, font, 1)  
      local x_offset = math.max ((ICON_SIZE - time_len) / 2, 0)
      local y_offset = math.max ((ICON_SIZE - font_height) / 2, 0)
      
      WindowText (win, font, time_left, x1 + x_offset + 2, y1 + y_offset + 2, x2, y2, 0x000000)
      WindowText (win, font, time_left, x1 + x_offset, y1 + y_offset, x2, y2, FONT_COLOUR)
    end -- show_time
    
  else
    buttons [n].cooldown_left = nil
  end -- if
  
  Redraw ()
  
end -- function SetCooldown

function OnPluginInstall ()

  local x, y, mode, flags = 
      tonumber (GetVariable ("windowx")) or 0,
      tonumber (GetVariable ("windowy")) or 0,
      tonumber (GetVariable ("windowmode")) or WINDOW_POSITION, -- top right
      tonumber (GetVariable ("windowflags")) or 0
  
  -- check miniwindow visible
  if x < 0 or x > GetInfo (281) or
     y < 0 or y > GetInfo (280) then
     x, y = 0, 0  -- reset to top left
  end -- if not visible
           
  win = GetPluginID ()  -- get a unique name
  tempwin = win .. ":temp"
  
  local gauge_height, gauge_width
  
  if horizontal then
    window_width = (#buttons * (ICON_SIZE + OFFSET)) + OFFSET
    window_height = ICON_SIZE + (OFFSET * 2)
  else
    window_width = ICON_SIZE + (OFFSET * 2)
    window_height = (#buttons * (ICON_SIZE + OFFSET)) + OFFSET
  end -- if
  
  -- make the miniwindow
  WindowCreate (win, 
             x, y,   -- left, top (auto-positions)
             window_width,     -- width
             window_height,  -- height
             mode,   -- position mode
             flags,  -- flags
             BACKGROUND_COLOUR) 
        
  -- for drawing cooldowns (window not visible)
  WindowCreate (tempwin, 
             0, 0,   -- left, top 
             ICON_SIZE,     -- width
             ICON_SIZE,  -- height
             12,   -- position mode
             0,  -- flags
             ColourNameToRGB "white") 

  -- grab fonts
  WindowFont (win, "f", FONT_NAME, FONT_SIZE, true) 
  WindowFont (win, "f2", FONT_NAME, FONT_SIZE_MEDIUM, true) 
  WindowFont (win, "f3", FONT_NAME, FONT_SIZE_SMALL, true) 
             
  -- draw the buttons
  
  for n, v in ipairs (buttons) do

    if v.icon then
      if WindowLoadImage (win, n, GetInfo (66) .. v.icon) ~= error_code.eOK then
          DoAfterSpecial (1, string.format ([[
              ColourNote ("white", "red", "Could not load image '%s'")]], 
                          string.gsub (GetInfo (66) .. v.icon, '\\', '\\\\')),
                          sendto.script)
      end -- if
    end -- if icon specified
       
    local x1, y1, x2, y2

    -- where to draw the icon
    if horizontal then
      x1, y1 = (n - 1) * (ICON_SIZE + OFFSET) + OFFSET, OFFSET
      x2, y2 = n * (ICON_SIZE + OFFSET), ICON_SIZE + OFFSET
    else
      x1, y1 = OFFSET, (n - 1) * (ICON_SIZE + OFFSET) + OFFSET
      x2, y2 = ICON_SIZE + OFFSET, n * (ICON_SIZE + OFFSET)
    end -- if
        
    -- draw the image
    if WindowDrawImage(win, n, 
                    x1, y1,   -- left, top
                    x2, y2,  -- right, bottom
                    2)  -- mode - stretch or shrink
        ~= error_code.eOK then
      WindowRectOp (win, 2, x1, y1,   -- left, top
                    x2, y2,  -- right, bottom
                    BACKGROUND_COLOUR)
    end -- if
               
    -- remember where to draw the frame, for mouse clicks
    frames [n] = { 
            x1 = x1 - 1,
            y1 = y1 - 1,
            x2 = x2 + 1,
            y2 = y2 + 1
            }
    
    -- draw the button border
    WindowRectOp (win, 1, 
                  frames [n].x1, frames [n].y1, frames [n].x2, frames [n].y2, 
                  BUTTON_EDGE) 
    
    -- make a hotspot we can click on
    WindowAddHotspot(win, n,  
                 frames [n].x1, frames [n].y1, frames [n].x2, frames [n].y2,   -- rectangle
                 "",   -- mouseover
                 "",   -- cancelmouseover
                 "mousedown",
                 "cancelmousedown", 
                 "mouseup", 
                 v.tooltip,  -- tooltip text
                 1, 0)  -- hand cursor
                      
  end --  for each world
  

  -- draw the border of the whole box
  WindowCircleOp (win, 2, 0, 0, 0, 0, BOX_COLOUR, 6, EDGE_WIDTH, 0x000000, 1) 
    
  -- make a hotspot
  WindowAddHotspot(win, "_",  
                   0, 0, 0, 0,   -- whole window
                   "",   -- MouseOver
                   "",   -- CancelMouseOver
                   "mousedown",
                   "",   -- CancelMouseDown
                   "",   -- MouseUp
                   "Drag to move",  -- tooltip text
                   10, 0)  -- arrow (NS/EW) cursor
                   
  WindowDragHandler(win, "_", "dragmove", "dragrelease", 0) 
  
  if GetVariable ("enabled") == "false" then
    ColourNote ("yellow", "", "Warning: Plugin " .. GetPluginName ().. " is currently disabled.")
    EnablePlugin (GetPluginID (), false)
    return
  end -- they didn't enable us last time
 
  -- ensure window visible
  WindowShow (win, true)
    
end -- OnPluginInstall

-- hide window on removal
function OnPluginClose ()
  WindowShow (win,  false)  -- hide it
end -- OnPluginClose

-- show window on enable
function OnPluginEnable ()
  WindowShow (win,  true)  -- show it
end -- OnPluginEnable

-- hide window on disable
function OnPluginDisable ()
  WindowShow (win,  false)  -- hide it
end -- OnPluginDisable

function OnPluginSaveState ()
  SetVariable ("enabled",     tostring (GetPluginInfo (GetPluginID (), 17)))
  SetVariable ("windowx",     WindowInfo (win, 10))
  SetVariable ("windowy",     WindowInfo (win, 11))
  SetVariable ("windowmode",  WindowInfo (win, 7))
  SetVariable ("windowflags", WindowInfo (win, 8))
end -- OnPluginSaveState

-- called every second on a timer
function handle_cooldowns (name)
  for n, v in ipairs (buttons) do
    if buttons [n].cooldown_left then
      SetCooldown (n, buttons [n].cooldown_left - 1) 
    end -- if some cooldown left
  end -- for
end -- function handle_cooldowns

]]>
</script>

</muclient>



The above plugin can be downloaded from: GitHub - Icon_Bar.xml - commit 1dd8ef7

The latest version is available at: GitHub - Icon_Bar.xml - master
Amended on Fri 10 Apr 2020 08:58 PM by Nick Gammon
Australia Forum Administrator #1

Example of it in operation. Without the time shown as text:

And now with the time shown as text:

Australia Forum Administrator #2
If you need to you can change the cooldown time in a script by calling SetCooldown. eg.


SetCooldown (4, 50) -- set button 4 to cooldown in 50 seconds


Also, if you need to cancel the cooldown (eg. because someone has cast a spell on you to clear cooldown times), you can do that by Shift+clicking on the appropriate button.
Australia Forum Administrator #3
The plugin shown above is now version 4, and has the following improvements:

  • If the miniwindow is not visible at startup (eg. because you resized the output window) it is repositioned at the top-left corner.
  • If the images files cannot be loaded an error message is shown after a 1-second delay - this lets you see the message if the plugin is loaded when the world loads.
  • If the images files cannot be loaded, the place where the image should be is cleared during each cooldown tick. Previously, with no image file available, the numbers were drawn on top of each other in a rather confusing way.

#4
(spam deleted)
Amended on Sat 16 Aug 2014 10:08 PM by Nick Gammon
#5
Still really new to all of this, but how would one adapt this to be at the bottom of your screen, like the Exp Bar plugin that Nick created?

Any info to point me in the right direction would be appreciated!
Australia Forum Administrator #6
First, in the plugin look for WINDOW_POSITION near the top. Nearby are some comments about "Useful positions" which you could use to change its position.

Probably for most flexibility you would incorporate the "movewindow" module which lets you drag a miniwindow to any position.
#7
Well, yeah, I could use it as a miniwindow, but I would rather have it clamped down there, so it doesn't hover over my prompt like a typical miniwindow. And I don't think it'd look right if it were under my text instead of over it.

Assuming it's possible to even do it that way?
USA #8
You can use the TextRectangle() function to resize the area of the output window that's used by the MUD output. Create just enough room below the output for your window, and move the bar there.
#9
Perfect! Thanks for the quick replies!
#10
If the images files cannot be loaded, the place where the image should be is cleared during each cooldown tick. Previously, with no image file available, the numbers were drawn on top of each other in a rather confusing way.
Australia Forum Administrator #11
I tested for a missing image in one place but not the other. I have amended the plugin above. In particular, where it said:


   WindowDrawImage(win, n, 
                  x1, y1,   -- left, top
                  x2, y2,  -- right, bottom
                  2)  -- mode - stretch or shrink


... it now says:


  if WindowDrawImage(win, n, 
                  x1, y1,   -- left, top
                  x2, y2,  -- right, bottom
                  2)  -- mode - stretch or shrink
    ~= error_code.eOK then
    WindowRectOp (win, 2, x1, y1,   -- left, top
                  x2, y2,  -- right, bottom
                  BACKGROUND_COLOUR)
  end



Netherlands #12
Great plugin, thanks for all the explanation. What I'm only wondering now is how I can put in some text like a small word in a button? I know I could also use it in the icon but then I'd have to make over thirty different icons with the text.
Thing is I have one bar of buttons that needs the cooldown on it, and I have this bar of buttons that actually doesn't need a cooldown necessarily.
On another note, how can I have it use a variable from another plugin? I.e. simply send "stab @target" won't do it. How do I make it retrieve that variable, I know it should have to do with PluginGetVariable as seen in the other button, but I'm unsure how to formulate that.
Amended on Sun 16 Jan 2011 04:20 PM by Krae
Australia Forum Administrator #13
Certainly you could put text on the button. That is how the cooldown numbers are done. Look in the plugin around where it says "-- if they want to see the time left ... do that now". You use WindowText with an appropriate font, and positioned where you want it, to do that. Naturally you draw the button first and the text second.

As for getting the variable from another plugin, this is the general idea:


target = GetPluginVariable ("f8f2cedd9f981f80b491666c", "target")


(Where "f8f2cedd9f981f80b491666c" is the plugin ID of the other plugin).

Now you have the "target" variable from a different plugin, you use it in the Send function or whatever you want to do with it.
Amended on Sun 16 Jan 2011 09:54 PM by Nick Gammon
Netherlands #14
I know it's possible, but I can't for the life of me figure out what would be better:
I have a button that will basically put up a defense when clicked, the defense will wear off after some time. I wanted to make a trigger of the line when it wears off so that it raises it automatically, however the visual cooldown is very useful so I was either contemplating of making a trigger in mushclient self that would also activate the button cooldown, or perhaps putting this trigger straight into the plugin, though I wouldn't quite know for sure how to do this, though it would involve the function of "SetCooldown [button number], [amount of seconds]" (I presume!)

On another note: I tried the above with getting the variable from another plugin, but I'm not sure on where I put the line "target = bladida" whether I put that inside the CDATA part

Right now I have it just above that (which doesn't work)


<!--  Script  -->

<script>

-- pull in entities outside the CDATA block

horizontal = ("&horizontal;"):lower ():sub (1, 1) == "y";
show_time = ("&show_time;"):lower ():sub (1, 1) == "y";
target = GetPluginVariable ("de59b5f58559443d5d593585", "target")

<![CDATA[

-- table of buttons


Thank you for your time and help. I appreciate it.
Amended on Mon 07 Feb 2011 06:42 PM by Krae
Australia Forum Administrator #15
For the target, in the plugin change the line:


      replacement = GetPluginVariable ("", s)    -- look up variable in global variables


to:


      replacement = GetPluginVariable ("de59b5f58559443d5d593585", s)    -- look up variable in plugin


That way, at the exact moment it wants the target, it looks it up in the relevant plugin
Australia Forum Administrator #16
For a trigger to cancel the cooldown in the plugin, something like this:


<triggers>
  <trigger
   enabled="y"
   match="Your fear wears off."
   send_to="12"
   sequence="100"
  >
  <send>SetCooldown (4, nil)</send>
  </trigger>
</triggers>


To add that to the plugin, copy and paste just before this line:


<!--  Script  -->


The simplest way is to add it to the plugin itself.




A more modular way is to make an alias that detects something from your main script file.

So instead of the above, do something like this:

First, add this alias to the plugin (in the place where I said to put the trigger):


<aliases>
  <alias
   match="cancel_cooldown * in_plugin"
   enabled="y"
   send_to="12"
   sequence="100"
  >
  <send>SetCooldown (%1, nil)</send>
  </alias>
</aliases>


Now add this to another plugin or your main world:


<triggers>
  <trigger
   enabled="y"
   match="Your fear wears off."
   omit_from_output="y"
   send_to="10"
   sequence="100"
  >
  <send>cancel_cooldown 4 in_plugin</send>
  </trigger>
</triggers>


Template:pasting
For advice on how to copy the above, and paste it into MUSHclient, please see Pasting XML.


What is going to happen here is:

  • The trigger matches the message: Your fear wears off.
  • The trigger sends "cancel_cooldown 4 in_plugin" to "execute" which means it is evaluated for aliases
  • The alias in the plugin detects "cancel_cooldown 4 in_plugin"
  • The alias cancels the cooldown


This two-step process lets a non-plugin send a "message" to a plugin. Of course, change the 4 to whatever button this particular message should be cancelling the cooldown for.
Netherlands #17
Wow

Thank you so much for the speedy reply! I had tried for several days and given up only to try for the past weeks again and finally deciding on asking for help.

Now I can work with this again!
I take it that I could also do the opposite by setting a cooldown remotely as well? As a crude demonstration:

match="You fear wears off"
send=SetCooldown (4, 60)


Or even make the aliases as such:


<aliases>
  <alias
   match="set_cooldown * * in_plugin"
   enabled="y"
   send_to="12"
   sequence="100"
  >
  <send>SetCooldown (%1, %2)</send>
  </alias>
  <alias
   match="cancel_cooldown * in_plugin"
   enabled="y"
   send_to="12"
   sequence="100"
  >
  <send>SetCooldown (%1, nil)</send>
  </alias>
</aliases>


Again thank you for your help, I'm still always amazed that the very creator just jumps in and helps out everytime.
Amended on Mon 07 Feb 2011 08:07 PM by Krae
Australia Forum Administrator #18
Yes, you have it exactly.
#19
Ok, I've been messing with it a little just to get used to changing the buttons' message they send and I can see how to do it but not with a menu(like the default "Special Attacks" button). Could you give me an examle of a button named "Attacks" that created a menu that would send "Jab", "Thrust" or "Lunge"? I just want to know this so I can see how it's supposed to work. Thanks =D
Australia Forum Administrator #20
Well it's just like in the example. Can you try it out and post what you have if it doesn't work?


  -- button 3
 {
  icon = "SparkIcon2.png",
  tooltip = "Special Attacks",
  script = function ()   -- script to execute when clicked
                  
    -- choose from menu
    local result = WindowMenu (win, 
      WindowInfo (win, 14),  -- x
      WindowInfo (win, 15),   -- y
      "Jab|Thrust|Lunge")
    
    -- if not cancelled, do action on current target
    if result ~= "" then
      Send (result:lower () .. " " .. GetPluginVariable ("", "target"))
    end -- if

  
          end,  -- script function
            
    cooldown = 10 * 60,  -- 10 minute cooldown
  }, -- end of button 3

#21
That's what I tried originally, but I got this:

Error number: 0
Event: Run-time error
Description: [string "Plugin"]:67: attempt to concatenate a nil value

stack traceback:

[string "Plugin"]:67: in function 'script'

[string "Plugin"]:217: in function <[string "Plugin"]:155>
Called by: Function/Sub: mouseup called by Plugin Icon_Bar

Reason: Executing plugin Icon_Bar sub mouseup

--------------------------------

P.S. I see what MIGHT be the problem. I don't want it to output "jab @target" or anything, I just want "Jab"
Australia Forum Administrator #22
Yes, that was designed to show how you might attack a target. Change:


Send (result:lower () .. " " .. GetPluginVariable ("", "target"))


to:



Send (result:lower ())



The "lower" bit just converts the word you choose into lower case.
#23
Yes, thank you. That worked but if I happen to have any problems again, I'll not them. Thanks =)
#24
Ok, I read the post earlier about how he would like to have the Action Bar docked at the bottom while not hovering over his prompt(Sort of like the XP Bar plugin) and I was wondering how woudl I go about doing that?


P.S. I'm still new when it comes to scripting MUSHclient, so I apologize for all the questions lately
Netherlands #25
Actually, that part is explained within the plugin itself!
Just find this bit and then change the window position as you want.. remember you can click and drag it too if you want to, it -should- memorize where you last dragged it I think, unless you reload the plugin.


-- where to put the window
WINDOW_POSITION = 6  -- top right
OFFSET = 6  -- gap inside box
EDGE_WIDTH = 2 -- size of border stroke

--[[
Useful positions:

4 = top left
5 = center left-right at top
6 = top right
7 = on right, center top-bottom
8 = on right, at bottom
9 = center left-right at bottom
--]]


In other news! Still loving this plugin a lot.
One thing I'm wondering now is if it would be possible to make a button send/execute an alias when clicked? Since I have attack aliases that go through a script which isn't important. Right now I'm using macros that execute the aliases, but it'd also be nifty if I had a bar of buttons that do the same when clicked.
South Korea #26
Hi Nick,

Thank you so much for this great script. I've been using the basic functions and they are wonderful, but I need some help to properly code the drop-down menu button for a variable spell vs attacking a target. In essence, what the spell does is choose a damage type (from the drop-down menu) then casts the spell to set that damage type. Example is click on button, choose earth, "cast 'spell string' earth" - but I'm having trouble modifying the attack target functions to apply to that kind of spell variable function. Here is the code section for that button:

-- button 6
{
icon = "gaiasfocus.png",
tooltip = "Gaias Focus",
script = function () -- script to execute when clicked

-- choose from menu
local result = WindowMenu (win,
WindowInfo (win, 14), -- x
WindowInfo (win, 15), -- y
"Air|Earth|Cold|Water")

-- if not cancelled, do action on current target
if result ~= "" then
Send (result:lower () .. " " .. GetPluginVariable ("", "target"))


end -- if


end, -- script function

cooldown = 1 * 60 * 15, -- 15 minute cooldown
}, -- end of button 6

Please help modify this code for me to allow for the casting of the spell (cast 'gaias focus' [damtype]) as determined by the player clicking on their desired damtype variable.

Cheers!
USA Global Moderator #27
Musashi, right now if you choose Air what you're sending is "air @target" (where "target" is a variable that you have to create that stores your target mob name) when what you want is to send "cast 'gaias focus' air" without even using a target.

so instead of

Send (result:lower () .. " " .. GetPluginVariable ("", "target"))

use

Send ("cast 'gaias focus' "..result:lower())


result contains your selection choice.
Amended on Mon 15 Aug 2011 03:44 PM by Fiendish
#28
Hi Nick.

Thanks for this GREAT plugin!

I've modified it for my own use, it uses triggers to catch
cooldowns on my ship's weapons, and lets me know when they
are ready to fire again.

I added support for the tts plugin, so instead of using
sounds the plugin talks to me, and changed to so if you
click on a button that doesn't have a send or script, it
does not start the cooldown.

Since cooldowns can change based on my in-game stats, I've
also made it so cooldowns are re-calculated on the fly if
needed, and saved across sessions.

I'm wondering if there is a way to make separate rows of
buttons, though?

Using the code I'm attaching below, you can see I have 10
buttons total, of 3 different varieties (Disruptor 1/2,4
Forward and 4 aft torpedoes).

I'd like to be able to have the aft torpedo buttons below
the others.. is something like this possible, or maybe I
just need to make a separate window and dock it below the
first?

Code is too big to post here, I've uploaded it to pastebin:
http://pastebin.com/e8GFck1H
Amended on Fri 27 Jun 2014 12:54 PM by Kovarid
Australia Forum Administrator #29
You could probably do multiple lines fairly easily by fiddling with this code in the middle:


    -- where to draw the icon
    if horizontal then
      x1, y1 = (n - 1) * (ICON_SIZE + OFFSET) + OFFSET, OFFSET
      x2, y2 = n * (ICON_SIZE + OFFSET), ICON_SIZE + OFFSET
    else
      x1, y1 = OFFSET, (n - 1) * (ICON_SIZE + OFFSET) + OFFSET
      x2, y2 = ICON_SIZE + OFFSET, n * (ICON_SIZE + OFFSET)
    end -- if


That calculates the x and y position of each button. Maybe if "n" is over a certain amount add something to "y1/y2" and subtract something from "x1/x2" to bring you down to line 2.

Plus you would have to make the overall window larger in the y direction.

Or, just make two plugins with different plugin IDs.
USA Global Moderator #30
Someone wanted help using text instead of icons as mentioned in one of the previous comments, so I made the necessary modifications for that.

Summary of changes:
* Moved the text printing into a separate function so that it could be used in multiple places.
* Split ICON_SIZE into separate ICON_WIDTH and ICON_HEIGHT values so that they don't have to be squares.
* Loaded a few extra font sizes and weights.
* Tweaked colors.
* If there is a "text" field in the button instead of an "icon" field, then it will print that text. (I left the "icon" fields there as nil below, but nil is the same as not having it in the table at all)



<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE muclient [
  <!ENTITY horizontal "y" > 
  <!ENTITY show_time "y" >
]>

<muclient>
<plugin
   name="Icon_Bar"
   author="Nick Gammon"
   id="ede0920fc1173d5a03140f0e"
   language="Lua"
   purpose="Shows a bar of buttons you can click on to do things"
   date_written="2009-02-26 09:00"
   date_modified="2010-05-31 17:22"
   requires="4.40"
   save_state="y"
   version="5.0"
   >
   
<description trim="y">
<![CDATA[
Install this plugin to show a button bar.

Click on an icon to execute a script or send a command to the MUD.

]]>
</description>

</plugin>

<!--  Timers  -->

<timers>
  <timer 
    script="handle_cooldowns" 
    enabled="y" second="1.00" 
    active_closed="y" >
  </timer>
</timers>


<!--  Script  -->

<script>

-- pull in entities outside the CDATA block

horizontal = ("&horizontal;"):lower ():sub (1, 1) == "y";
show_time = ("&show_time;"):lower ():sub (1, 1) == "y";


<![CDATA[

-- table of buttons

--[[
  Each button can have:
  
  icon - filename of the image to draw
  text - text to write if no icon is set
  tooltip - what to show if you hover the mouse over the button
  send - what to send to the MUD
  script - a script function to call
  cooldown - time spell takes to cool down, in seconds
  sound - sound to play when button pressed

--]]
  

buttons = {

  -- button 1
  {
  icon = nil,
  tooltip = "Heal self",  -- tooltip help text
  text = "Heal",
  send = "echo doing heal",  -- what to send
  cooldown = 10,          -- cooldown time in seconds
  sound = "chimes.wav",   -- sound to play when cast
  }, -- end of button 1
  
  -- button 2
  {
  icon = nil,
  text = "Attack",
  tooltip = "Attack the darkness",  -- tooltip help text
  send = "echo doing attack",
  cooldown = 5,
  }, -- end of button 2
  
 --> add more buttons here
 
  
} -- end of buttons table


-- configuration

ICON_WIDTH = 48
ICON_HEIGHT = 32

BACKGROUND_COLOUR = ColourNameToRGB "dimgray"
BOX_COLOUR = ColourNameToRGB "royalblue"
BUTTON_EDGE = ColourNameToRGB "silver"

MOUSE_DOWN_COLOUR = ColourNameToRGB "darkorange"

-- where to put the window
WINDOW_POSITION = 6  -- top right
OFFSET = 6  -- gap inside box
EDGE_WIDTH = 2 -- size of border stroke

--[[
Useful positions:

4 = top left
5 = center left-right at top
6 = top right
7 = on right, center top-bottom
8 = on right, at bottom
9 = center left-right at bottom
--]]

-- font and size to use

FONT_NAME = "Lucida Sans Unicode"
FONT_SIZE = 18
FONT_SIZE_MEDIUM = 14
FONT_SIZE_SMALL = 10
FONT_SIZE_SMALLEST = 8
TEXT_COLOUR = ColourNameToRGB "white"
FADED_TEXT_COLOUR = ColourNameToRGB "darkgray"
COOLDOWN_TEXT_COLOUR = ColourNameToRGB "yellow"

-- sound to play if on cooldown
ON_COOLDOWN_SOUND = "ding.wav"

frames = {}  -- remember where each icon was

require "commas"

function mousedown (flags, hotspot_id)

  if hotspot_id == "_" then
  
    -- find where mouse is so we can adjust window relative to mouse
    startx, starty = WindowInfo (win, 14), WindowInfo (win, 15)
    
    -- find where window is in case we drag it offscreen
    origx, origy = WindowInfo (win, 10), WindowInfo (win, 11)
  
    return
    end -- if
    
    
  local n = tonumber (hotspot_id)
  
  -- draw the button border in another colour for visual feedback
  WindowRectOp (win, 1, 
                frames [n].x1, frames [n].y1, frames [n].x2, frames [n].y2, 
                MOUSE_DOWN_COLOUR) 
  
  Redraw ()                
end -- mousedown

function cancelmousedown (flags, hotspot_id)
  local n = tonumber (hotspot_id)
  
  -- draw the button border in original colour for visual feedback
  WindowRectOp (win, 1, 
                frames [n].x1, frames [n].y1, frames [n].x2, frames [n].y2, 
                BUTTON_EDGE) 
  Redraw ()                

end -- cancelmousedown

function mouseup (flags, hotspot_id)
   
  -- fix border colour
  cancelmousedown (flags, hotspot_id)
  
  local n = tonumber (hotspot_id)
  
  local button = buttons [n]
  
  -- shift key clears cooldown
  if bit.band (flags, 1) == 1 then
    SetCooldown (n, nil)
    return
  end -- if
  
  -- can't press button if on cooldown
  if (button.cooldown_left or 0) > 0 then
    Sound (ON_COOLDOWN_SOUND)
    return
  end -- still on cooldown
  
  -- play sound if defined
  if button.sound then
    Sound (button.sound)
  end -- sound to play
  
  -- send to world if something specified
  if type (button.send) == "string" and 
    button.send ~= "" then
     
    local errors =  {} -- no errors yet
  
    -- function to do the replacements for string.gsub
    
    local function replace_variables (s)
      s = string.sub (s, 2)  -- remove the @
      replacement = GetPluginVariable ("", s)    -- look up variable in global variables
      if not replacement then  -- not there, add to errors list
        table.insert (errors, s)
        return
      end -- not there
      return replacement  -- return variable
    end -- replace_variables 
    
    -- replace all variables starting with @
    
    local command = string.gsub (button.send, "@%a[%w_]*", replace_variables)
    
    -- report any errors
    
    if #errors > 0 then
      for k, v in ipairs (errors) do
        ColourNote ("white", "red", "Variable '" .. v .. "' does not exist")
      end -- for
      return
    end -- error in replacing
     
    Execute (command)
  end -- if
  
  -- execute script if wanted
  if type (button.script) == "function" then
    button.script (n) 
  end -- if
     
  SetCooldown (n, button.cooldown)
  
end -- mouseup

function dragmove(flags, hotspot_id)

  -- find where it is now
  local posx, posy = WindowInfo (win, 17),
                     WindowInfo (win, 18)

  -- move the window to the new location
  WindowPosition(win, posx - startx, posy - starty, 0, 2);
  
  -- change the mouse cursor shape appropriately
  if posx < 0 or posx > GetInfo (281) or
     posy < 0 or posy > GetInfo (280) then
    SetCursor (11)   -- X cursor
  else
    SetCursor (10)   -- arrow (NS/EW) cursor
  end -- if
  
end -- dragmove

function dragrelease(flags, hotspot_id)
  local newx, newy = WindowInfo (win, 17), WindowInfo (win, 18)
  
  -- don't let them drag it out of view
  if newx < 0 or newx > GetInfo (281) or
     newy < 0 or newy > GetInfo (280) then
     -- put it back
    WindowPosition(win, origx, origy, 0, 2);
  end -- if out of bounds
  
end -- dragrelease

function SetCooldown (n, amount)
  assert (n >= 1 and n <= #buttons, 
          "Bad button number " .. n .. " to SetCooldown")
  if amount then
    assert (amount >= 0, "Bad amount " .. amount .. " to SetCooldown")
  end -- if
          
  local frame = frames [n] 
  local x1, y1, x2, y2 = frame.x1 + 1, frame.y1 + 1, frame.x2 - 1, frame.y2 - 1
  buttons [n].cooldown_left = amount  -- cooldown time left in seconds
  local max = buttons [n].cooldown or 0   -- max cooldown time
  local percent  -- how far cooled down we are as a percent
  
  if max > 0 then
    percent = (amount or 0) / max 
  else
    percent = 0  -- don't divide by zero!
  end -- if
  
  -- reload the image
  if WindowDrawImage(win, n, 
                  x1, y1,   -- left, top
                  x2, y2,  -- right, bottom
                  2)  -- mode - stretch or shrink
    ~= error_code.eOK then
    WindowRectOp (win, 2, x1, y1,   -- left, top
                  x2, y2,  -- right, bottom
                  BACKGROUND_COLOUR)
    local button_text = buttons[n].text
    if button_text then
      AddText(button_text, n, false, (amount == 0) and TEXT_COLOUR or FADED_TEXT_COLOUR)
    end
  end
                 
  if amount and amount > 0 then
  
    -- calculate pie end point
    local endx = math.cos (math.rad (percent * 360 + 90)) * ICON_WIDTH + ICON_WIDTH / 2
    local endy = -1 * math.sin (math.rad (percent * 360 + 90)) * ICON_HEIGHT + ICON_HEIGHT / 2
    
    -- clear temporary window
    WindowRectOp (tempwin, 2, 0, 0, 0, 0, 0xFFFFFF)  -- fill with white
    
    -- draw the pie showing amount of cooldown
    WindowCircleOp (tempwin, 5, -10, -10, ICON_WIDTH + 10, ICON_HEIGHT + 10,   -- pie
          0x000000, 5, 0,   -- no pen
          0x000000, 0,  -- solid brush, black
          ICON_WIDTH / 2, 0,   -- from 12 o'clock position
          endx, endy)
    
    -- turn pie shape into an image
    WindowImageFromWindow(win, "mask", tempwin)
                      
    -- blend in (darken mode) with 50% opacity
    WindowBlendImage(win, "mask", 
                     x1, y1, x2, y2,  -- rectangle
                     5,  -- darken
                     0.5)  -- opacity
                  
    -- if they want to see the time left (text on top of the button) do that now   
    if show_time then
      local time_left = convert_time (amount)
      time_left = string.gsub (time_left, "[ s]", "") -- get rid of spaces, and "s"
      AddText (time_left, n, true, COOLDOWN_TEXT_COLOUR)
    end -- show_time
    
  else
    buttons [n].cooldown_left = nil
  end -- if
  
  Redraw ()
  
end -- function SetCooldown

function AddText (text, n, bold, colour)
  local frame = frames [n] 
  local x1, y1, x2, y2 = frame.x1 + 1, frame.y1 + 1, frame.x2 - 1, frame.y2 - 1
  local font = bold and "f1" or "f5"
  local text_len = WindowTextWidth (win, font, text)
  
  -- use smaller font if it doesn't fit
  if text_len > ICON_WIDTH then
    font = bold and "f2" or "f6"
    text_len = WindowTextWidth (win, font, text)

    -- still too big?
    if text_len > ICON_WIDTH then
      font = bold and "f3" or "f7"
      text_len = WindowTextWidth (win, font, text)

      -- still too big?
      if text_len > ICON_WIDTH then
        font = bold and "f4" or "f8"
        text_len = WindowTextWidth (win, font, text)
      end -- if
    end -- if

  end -- if

  local font_height = WindowFontInfo (win, font, 1)  
  local x_offset = math.max ((ICON_WIDTH - text_len) / 2, 0)
  local y_offset = math.max ((ICON_HEIGHT - font_height) / 2, 0)
  
  WindowText (win, font, text, x1 + x_offset + 2, y1 + y_offset + 2, x2, y2, 0x000000)
  WindowText (win, font, text, x1 + x_offset, y1 + y_offset, x2, y2, colour)
end

function OnPluginInstall ()

  local x, y, mode, flags = 
      tonumber (GetVariable ("windowx")) or 0,
      tonumber (GetVariable ("windowy")) or 0,
      tonumber (GetVariable ("windowmode")) or WINDOW_POSITION, -- top right
      tonumber (GetVariable ("windowflags")) or 0
  
  -- check miniwindow visible
  if x < 0 or x > GetInfo (281) or
     y < 0 or y > GetInfo (280) then
     x, y = 0, 0  -- reset to top left
  end -- if not visible
           
  win = GetPluginID ()  -- get a unique name
  tempwin = win .. ":temp"
  
  local gauge_height, gauge_width
  
  if horizontal then
    window_width = (#buttons * (ICON_WIDTH + OFFSET)) + OFFSET
    window_height = ICON_HEIGHT + (OFFSET * 2)
  else
    window_width = ICON_WIDTH + (OFFSET * 2)
    window_height = (#buttons * (ICON_HEIGHT + OFFSET)) + OFFSET
  end -- if
  
  -- make the miniwindow
  WindowCreate (win, 
             x, y,   -- left, top (auto-positions)
             window_width,     -- width
             window_height,  -- height
             mode,   -- position mode
             flags,  -- flags
             BACKGROUND_COLOUR) 
        
  -- for drawing cooldowns (window not visible)
  WindowCreate (tempwin, 
             0, 0,   -- left, top 
             ICON_WIDTH,     -- width
             ICON_HEIGHT,  -- height
             12,   -- position mode
             0,  -- flags
             ColourNameToRGB "white") 

  -- grab bold fonts
  WindowFont (win, "f1", FONT_NAME, FONT_SIZE, true)
  WindowFont (win, "f2", FONT_NAME, FONT_SIZE_MEDIUM, true)
  WindowFont (win, "f3", FONT_NAME, FONT_SIZE_SMALL, true)
  WindowFont (win, "f4", FONT_NAME, FONT_SIZE_SMALLEST, true)
  -- grab not-bold fonts
  WindowFont (win, "f5", FONT_NAME, FONT_SIZE, false)
  WindowFont (win, "f6", FONT_NAME, FONT_SIZE_MEDIUM, false)
  WindowFont (win, "f7", FONT_NAME, FONT_SIZE_SMALL, false)
  WindowFont (win, "f8", FONT_NAME, FONT_SIZE_SMALLEST, false)
  

  -- draw the buttons
  
  for n, v in ipairs (buttons) do

    if v.icon then
      if WindowLoadImage (win, n, GetInfo (66) .. v.icon) ~= error_code.eOK then
          DoAfterSpecial (1, string.format ([[
              ColourNote ("white", "red", "Could not load image '%s'")]], 
                          string.gsub (GetInfo (66) .. v.icon, '\\', '\\\\')),
                          sendto.script)
      end -- if
    end -- if icon specified
       
    local x1, y1, x2, y2

    -- where to draw the icon
    if horizontal then
      x1, y1 = (n - 1) * (ICON_WIDTH + OFFSET) + OFFSET, OFFSET
      x2, y2 = n * (ICON_WIDTH + OFFSET), ICON_HEIGHT + OFFSET
    else
      x1, y1 = OFFSET, (n - 1) * (ICON_HEIGHT + OFFSET) + OFFSET
      x2, y2 = ICON_WIDTH + OFFSET, n * (ICON_HEIGHT + OFFSET)
    end -- if
    
    -- remember where to draw the frame, for mouse clicks
    frames [n] = { 
      x1 = x1 - 1,
      y1 = y1 - 1,
      x2 = x2 + 1,
      y2 = y2 + 1
    }
    
    -- draw the image
    if WindowDrawImage(win, n, 
                    x1, y1,   -- left, top
                    x2, y2,  -- right, bottom
                    2)  -- mode - stretch or shrink
        ~= error_code.eOK then
      WindowRectOp (win, 2, x1, y1,   -- left, top
                    x2, y2,  -- right, bottom
                    BACKGROUND_COLOUR)
      if v.text then
        AddText(v.text, n, false, TEXT_COLOUR)
      end
    end -- if
    
    -- draw the button border
    WindowRectOp (win, 1, 
                  frames [n].x1, frames [n].y1, frames [n].x2, frames [n].y2, 
                  BUTTON_EDGE) 
    
    -- make a hotspot we can click on
    WindowAddHotspot(win, n,  
                 frames [n].x1, frames [n].y1, frames [n].x2, frames [n].y2,   -- rectangle
                 "",   -- mouseover
                 "",   -- cancelmouseover
                 "mousedown",
                 "cancelmousedown", 
                 "mouseup", 
                 v.tooltip,  -- tooltip text
                 1, 0)  -- hand cursor
                      
  end --  for each world
  

  -- draw the border of the whole box
  WindowCircleOp (win, 2, 0, 0, 0, 0, BOX_COLOUR, 6, EDGE_WIDTH, 0x000000, 1) 
    
  -- make a hotspot
  WindowAddHotspot(win, "_",  
                   0, 0, 0, 0,   -- whole window
                   "",   -- MouseOver
                   "",   -- CancelMouseOver
                   "mousedown",
                   "",   -- CancelMouseDown
                   "",   -- MouseUp
                   "Drag to move",  -- tooltip text
                   10, 0)  -- arrow (NS/EW) cursor
                   
  WindowDragHandler(win, "_", "dragmove", "dragrelease", 0) 
  
  if GetVariable ("enabled") == "false" then
    ColourNote ("yellow", "", "Warning: Plugin " .. GetPluginName ().. " is currently disabled.")
    EnablePlugin (GetPluginID (), false)
    return
  end -- they didn't enable us last time
 
  -- ensure window visible
  WindowShow (win, true)
    
end -- OnPluginInstall

-- hide window on removal
function OnPluginClose ()
  WindowShow (win,  false)  -- hide it
end -- OnPluginClose

-- show window on enable
function OnPluginEnable ()
  WindowShow (win,  true)  -- show it
end -- OnPluginEnable

-- hide window on disable
function OnPluginDisable ()
  WindowShow (win,  false)  -- hide it
end -- OnPluginDisable

function OnPluginSaveState ()
  SetVariable ("enabled",     tostring (GetPluginInfo (GetPluginID (), 17)))
  SetVariable ("windowx",     WindowInfo (win, 10))
  SetVariable ("windowy",     WindowInfo (win, 11))
  SetVariable ("windowmode",  WindowInfo (win, 7))
  SetVariable ("windowflags", WindowInfo (win, 8))
end -- OnPluginSaveState

-- called every second on a timer
function handle_cooldowns (name)
  for n, v in ipairs (buttons) do
    if buttons [n].cooldown_left then
      SetCooldown (n, buttons [n].cooldown_left - 1) 
    end -- if some cooldown left
  end -- for
end -- function handle_cooldowns

]]>
</script>

</muclient>


The above modified plugin can be downloaded from: GitHub - Icon_Bar.xml - commit 6955ede

The latest version is available at: GitHub - Icon_Bar.xml - master
Amended on Fri 10 Apr 2020 08:53 PM by Nick Gammon
Australia Forum Administrator #31

As requested here I have amended it to have a “done” function which is called when the time is up. Download from https://raw.githubusercontent.com/nickgammon/plugins/master/Icon_Bar.xml

As shown in the comments you can add a function call to the bar like this:

  done = function (n) print ("Healing done!") end   -- what to do when done

The function can be inline (like above) or a function elsewhere in your code. The function is passed the number of the icon bar which has reached its cooldown time.