[Home] [Downloads] [Search] [Help/forum]

Gammon Software Solutions forum

See www.mushclient.com/spam for dealing with forum spam. Please read the MUSHclient FAQ!

[Folder]  Entire forum
-> [Folder]  MUSHclient
. -> [Folder]  Plugins
. . -> [Subject]  Improved action bar plugin, with cooldowns
Home  |  Users  |  Search  |  FAQ
Username:
Register forum user name
Password:
Forgotten password?

Improved action bar plugin, with cooldowns

[Reply to this subject]  Reply to this subject   [New subject]  Start a new subject   [Refresh] Refresh page


Posted by Nick Gammon   Australia  (15,394 posts)  [Biography] bio
Date Mon 30 Mar 2009 08:26 PM (UTC)  quote  ]

Amended on Mon 31 May 2010 07:23 AM (UTC) by Nick Gammon

Message
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>


- Nick Gammon

www.gammon.com.au, www.mushclient.com
[Go to top] top

Posted by Nick Gammon   Australia  (15,394 posts)  [Biography] bio
Date Mon 30 Mar 2009 08:29 PM (UTC)  quote  ]
Message

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

And now with the time shown as text:


- Nick Gammon

www.gammon.com.au, www.mushclient.com
[Go to top] top

Posted by Nick Gammon   Australia  (15,394 posts)  [Biography] bio
Date Mon 30 Mar 2009 08:31 PM (UTC)  quote  ]
Message
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.

- Nick Gammon

www.gammon.com.au, www.mushclient.com
[Go to top] top

Posted by Nick Gammon   Australia  (15,394 posts)  [Biography] bio
Date Thu 02 Apr 2009 01:18 AM (UTC)  quote  ]
Message
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.


- Nick Gammon

www.gammon.com.au, www.mushclient.com
[Go to top] top

Posted by George.mikal   (1 post)  [Biography] bio
Date Thu 22 Apr 2010 05:15 AM (UTC)  quote  ]
Message
thanks for sharing script here..

george
[Go to top] top

Posted by Bala   (5 posts)  [Biography] bio
Date Wed 26 May 2010 07:02 AM (UTC)  quote  ]
Message
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!
[Go to top] top

Posted by Nick Gammon   Australia  (15,394 posts)  [Biography] bio
Date Wed 26 May 2010 07:23 AM (UTC)  quote  ]
Message
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.

- Nick Gammon

www.gammon.com.au, www.mushclient.com
[Go to top] top

Posted by Bala   (5 posts)  [Biography] bio
Date Wed 26 May 2010 08:04 AM (UTC)  quote  ]
Message
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?
[Go to top] top

Posted by Twisol   USA  (1,277 posts)  [Biography] bio
Date Wed 26 May 2010 08:08 AM (UTC)  quote  ]
Message
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.

'Soludra' on Achaea
Maps: http://achaea.jonathan.com (still under development)

MiniWidget Framework: http://github.com/Twisol/MUSHclient-MWidget
[Go to top] top

Posted by Bala   (5 posts)  [Biography] bio
Date Wed 26 May 2010 08:22 AM (UTC)  quote  ]
Message
Perfect! Thanks for the quick replies!
[Go to top] top

Posted by Williamsmith   (1 post)  [Biography] bio
Date Mon 31 May 2010 04:50 AM (UTC)  quote  ]
Message
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.

William smith
[Go to top] top

Posted by Nick Gammon   Australia  (15,394 posts)  [Biography] bio
Date Mon 31 May 2010 07:25 AM (UTC)  quote  ]
Message
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




- Nick Gammon

www.gammon.com.au, www.mushclient.com
[Go to top] top

The dates and times for posts above are shown in Universal Co-ordinated Time (UTC).

To show them in your local time you can join the forum, and then set the 'time correction' field in your profile to the number of hours difference between your location and UTC time.


3,615 views.

[Reply to this subject]  Reply to this subject   [New subject]  Start a new subject   [Refresh] Refresh page

Go to topic:           Search the forum


[Go to top] top

[Home]

Written by Nick Gammon - 5K

Comments to: Gammon Software support
[RH click to get RSS URL] Forum RSS feed ( http://www.gammon.com.au/rss/forum.xml )

[Best viewed with any browser - 2K]    [Internet Contents Rating Association (ICRA) - 2K]    [Web site powered by FutureQuest.Net]