<?xml version="1.0" encoding="iso-8859-1"?>
<!DOCTYPE muclient>


<muclient>
<plugin
   name="Aardwolf_Spellups"
   author="Nick Gammon"
   id="b0a9cef2629fae2eacf97603"
   language="Lua"
   purpose="Does Aardwolf spellups"
   date_written="2008-07-05"
   requires="4.30"
   version="1.02"
   save_state="y"
   >
<description trim="y">
spellups            --> display current spellups in output window

spellup all         --> cast all possible spellups
spellup none        --> remove all spellups

spellup a,b,c       --> add spells a, b, c in that order 
                      (eg. spellup shield, blur, detect magic)
                      (or: spellup 72, 171, 35)
(or)
Spellups: a, b, c   --> add spells a, b, c 
                      (you can copy and paste output from current list)

spellup + blur, avoid:3    --> add more to the current list
  to add a spell in a certain place add a : then the place number
spellup - night vision, detect magic   --> remove those from the list

spellup fast    --> cast all spellups immediately, as fast as you can
spellup pause   --> stop casting spellups until a resume
spellup resume  --> resume casting
spellup refresh --> requery server for current spells on us

spellup brief   --> show summary of spellup situation
spellup full    --> show full details

spellup other (name) --> try to spellup another player with all spellups
                          (eg. spellup other johnsmith)
                        
spellup block   --> block a spell with another spell
spellup disable --> disable a spell without taking it out of the list

spellup help    --> this message
</description>


</plugin>

<triggers>

  <trigger
   enabled="y"
   match="Welcome to Aardwolf. May your adventures be mystical, challenging and rewarding."
   sequence="100"
   send_to="12"   
  >
  <send>
  spellup_refresh() 
  </send>
  </trigger>
  
  <trigger
   enabled="y"
   match="############# Reconnecting to Game #############"
   sequence="100"
   send_to="12"   
  >
  <send>
  spellup_refresh() 
  </send>
  </trigger>
</triggers>

<!--  Shared script stuff  -->


<script>
<![CDATA[

affect_world = "Spellups"
folder = "Aardwolf"

MAX_RETRIES = 10  -- max tries we will allow them to fail to cast

require "getworld"
require "tprint"
require "commas"
require "addxml"
require "checkplugin"
require "serialize"  
require "utils"

current_buffs = {}   -- what they currently have on them
cooldowns = {}       -- what is on cooldown
cast_attempt_count = {}  -- what we tried to cast
failed_attempt_count = {}  -- what we failed to cast
skip = {}            -- ones we have decided to ignore for now
stats = {}           -- table will be replaced by Stats_Detector plugin
fdebug = false
status = {
  ["state"] = 1,
  ["statestring"] = 'login',
}

last_buff = os.time ()
disablednum = -1
waiting = -1

-- the next 3 items are saved to the plugin state file


-- known spells and recoveries indexed by number
spells = {}      -- spells that exist
recoveries = {}  -- recoveries that exist
wanted_buffs = {}  -- what spellups they want
blockers = {}

-- cross-reference for spells - given a name, returns a number
spells_xref = {}
recoveries_xref = {}


function justWords(str)
  local t = {}
  local function helper(word) table.insert(t, word) return "" end
  if not str:gsub("%w+", helper):find"%S" then return t end
end

function capitalize (s)
  return string.sub (s, 1, 1):upper () .. string.sub (s, 2):lower ()
end -- capitalize 

function pdebug(...)
  if fdebug then
    print(...)
  end
end -- pdebug

function PrefixCheck (t, s)
  for name, item in pairs (t) do
    if string.match (name, "^" .. s) then -- prefix match, so "avoid" matches "avoidance"
      return name, item
    end -- if name matches
  end -- checking table
  return nil  -- not found
end -- PrefixCheck

function remove_spellup (sn)
  for i, buff in ipairs (wanted_buffs) do
    if sn == buff then
      table.remove (wanted_buffs, i)
      return true
    end -- if
  end -- for
end -- remove_spellup

function isblocked(sn)
  if blockers[sn] then
    for _, blocker in ipairs(blockers[sn]) do
      if current_buffs[blocker] then
        return true
      end
    end
  end
  return false    
end

function add_blocker(sn, blk)
  if not blockers[sn] then
    table.insert(blockers, sn, {})
  end
  for i, blocker in ipairs(blockers[sn]) do
    if blocker == blk then
      return true
    end
  end
  table.insert(blockers[sn], blk)
  return true
end -- add_disabled

function remove_blocker(sn, blk)
  if not blockers[sn] then
    return
  end    
  for i, blocker in ipairs(blockers[sn]) do
    if blocker == blk then
      table.remove(blockers[sn], i)
      if #blockers[sn] == 0 then
        blockers[sn] = nil
      end
      return true
    end
  end
  ColourNote ("yellow", "", "spell " .. sn .. " is not blocked by " .. blk)
  return false
end

function isdisabled(sn)
  if blockers[sn] then
    for _, blocker in ipairs(blockers[sn]) do
      if blocker == disablednum then
        return true
      end
    end
  end
  return false    
end

function add_disabled(sn)
  return add_blocker(sn, disablednum)
end -- add_disabled

function remove_disabled(sn)
  return remove_blocker(sn, disablednum)
end

-- make tables that let us convert spell name to spell number
function make_xrefs ()
  spells_xref = {}
  for k, v in pairs (spells) do
    spells_xref [v.name] = k
  end -- for each spell
 
  recoveries_xref = {}
  for k, v in pairs (recoveries) do
    recoveries_xref [v.name] = k
  end -- for each recovery
end -- make_xrefs

function find_spellsn(item)
  if not item then
    ColourNote ("red", "", "A nil value was passed to find_spellsn")      
    return false
  end
  item = trim (item):lower ()
  local sn = tonumber (item)
  local name 
  invalid = false
  -- see if numeric spell numbner given
  if sn and not spells [sn] then
    ColourNote ("red", "", "Spell number '" .. item .. "' does not exist.")
    invalid = true
    sn = nil
  elseif not sn then
    -- look up word
    sn = spells_xref [item]  -- look for exact match first 
                             -- (otherwise "bless" might match "bless weapon")
    if not sn then
      _, sn = PrefixCheck (spells_xref, item)
    end -- not found by exact match
    if not sn then
      ColourNote ("red", "", "Spell named '" .. item .. "' does not exist.")
      invalid = true
    end -- name mot found
  end -- if
  return sn, invalid
end -- find_spell

--   PLUGIN INSTALL ---
function OnPluginInstall ()
  -- on plugin install, convert variable into Lua table
  assert (loadstring (GetVariable ("wanted_buffs") or "")) ()
  assert (loadstring (GetVariable ("spells") or "")) ()
  assert (loadstring (GetVariable ("recoveries") or "")) ()
  assert (loadstring (GetVariable ("blockers") or "")) ()
  
  make_xrefs ()

  if GetVariable ("enabled") == "false" then
    ColourNote ("yellow", "", "Warning: Plugin " .. GetPluginName ().. " is currently disabled.")
    check (EnablePlugin(GetPluginID (), false))
    return
  end -- they didn't enable us last time
  
  OnPluginEnable ()  -- do initialization stuff
end -- OnPluginInstall

function OnPluginDisconnect ()
  disconnected = true
  coroutine.resume (thread, "disconnect")  
end -- OnPluginDisconnect

-- pull in telnet option handling
dofile (GetPluginInfo (GetPluginID (), 20) .. "telnet_options.lua")
  
function OnPluginConnect ()
  TelnetOptionOn (TELOPT_SPELLUP)
  TelnetOptionOn (TELOPT_SKILLGAINS)
  disconnected = false
  have_slist = false
  thread = coroutine.create (buff_loop)
  --resume_buff_loop ("connect")
end -- function OnPluginConnect


function OnPluginClose ()
  -- if enabled
  if GetPluginInfo (GetPluginID (), 17) then
    TelnetOptionOff (TELOPT_SPELLUP)
    TelnetOptionOff (TELOPT_SKILLGAINS)
  end -- if enabled
end -- OnPluginClose


function OnPluginEnable ()
  checkplugin ("8a710e0783b431c06d61a54c", "Stats_Detector.xml")
  checkplugin ("f5b05e8826711cdb0d141939", "Playing_Detector.xml")
 
  -- ensure world file exists
  local w = get_a_world (affect_world, folder)
  if (w == nil) then
    CallPlugin ("35dfdbf3afc8cbf60c91277c", "CreateWorldFile", folder .. "," .. affect_world)
    local w = get_a_world (affect_world, folder)
  end
  
  if w then
    w:DeleteOutput ()  
    w:Note "Spellups will appear here."
    w:Note ""
    show_wanted (w)
    w:SetOption ("do_not_show_outstanding_lines", 1)
    w:SetCommandWindowHeight (0)  -- no command window
    w:SetWorldWindowStatus (3) -- restore it     
  end -- world

  -- if we are connected when the plugin loads, it must have been reloaded whilst playing
  if IsConnected () then
    TelnetOptionOn (TELOPT_REQUEST_STATUS) -- get actual status (eg. afk, playing)
    OnPluginConnect ()
  end -- if already connected
  
  -- see if we are playing at install time
  status = GetPluginVariableList("f5b05e8826711cdb0d141939")
  if status.statestring == "active" then
    Send "slist noprompt"
  end
end -- OnPluginEnable

function OnPluginDisable ()
  TelnetOptionOff (TELOPT_SPELLUP)
  TelnetOptionOff (TELOPT_SKILLGAINS)
  local w = get_a_world (affect_world, folder)
  if w then
    w:SetWorldWindowStatus (2) -- minimize it on disable
  end -- if 
end -- OnPluginDisable

function resume_buff_loop (reason, other_reason)
  pdebug("res_buf_loop: ", reason, other_reason)
  pdebug("Waiting: ", waiting)

  if disconnected then
    return
  end -- don't bother

  if waiting > 0 then
    if reason == "sfail" or reason == "affecton" then
      --pdebug("resume_buff_loop: Changing waiting to -1")
      --waiting = -1
    else
      return
    end
  end

  if coroutine.status (thread) ~= "suspended" then
    ColourNote ("red", "yellow", "Problem with spellup plugin - please reinstall.")
    return
  end -- if problem

  local ok, err = coroutine.resume (thread, reason, other_reason)
  if not ok then
     print (debug.traceback (thread))
     assert (ok, err)
  end -- if
end -- resume_buff_loop

--   SAVE STATE ---
function OnPluginSaveState ()
  SetVariable ("wanted_buffs", 
               "wanted_buffs = " .. serialize.save_simple (wanted_buffs))
  SetVariable ("blockers", 
               "blockers = " .. serialize.save_simple (blockers))
  SetVariable ("spells", 
               "spells = " .. serialize.save_simple (spells))
  SetVariable ("recoveries", 
               "recoveries = " .. serialize.save_simple (recoveries))
               
  SetVariable ("enabled", tostring (GetPluginInfo (GetPluginID (), 17)))
               
end -- function OnPluginSaveState

--[[
Spell targets are:
   0 : No target
   1 : Combat / Attack  
   2 : Spellup/cure - can cast on others.
   3 : Spellup/cure - self only.   
   4 : Objects.

Skilltype:
  1 for spell,
  2 for skill.

 --]]
 
-- parse spellheaders line, break into pieces
function parse_spell_line (line)
  local sn, name, target, duration, percent, recovery, skilltype = 
    string.match (line, "^(%d+)%,([A-Za-z0-9 ]+)%,(%d+)%,(%d+)%,(%d+),(-?%d+),(%d+)$")
  if not sn then
    ColourNote ("white", "re,d", "Invalid spellheaders line: " .. line)
    return nil
  end -- not a valid spell name
  
  return tonumber (sn), 
         trim (name:lower()),
         tonumber (target),
         tonumber (duration),
         tonumber (percent),
         tonumber (recovery),
         tonumber (skilltype)
         
end -- parse_spell_line

-- parse recoveries line, break into pieces
function parse_recoveries_line (line)
  local sn, name, duration = string.match (line, "^(%d+)%,([A-Za-z0-9 ]+)%,(%d+)$")

  if not sn then
    ColourNote ("white", "red", "Invalid recoveries line: " .. line)
    return nil
  end -- not a valid spell name
  
  return tonumber (sn), trim (name:lower ()), tonumber (duration)
end -- parse_recoveries_line

function OnPluginBroadcast (msg, id, name, text)
  local old_mana = stats.mana
  local old_moves = stats.moves
  local new_stats = false
  local old_state = status.state
 
  -- stats change
  if id == "8a710e0783b431c06d61a54c" then
    if not next(stats) then
      new_stats = true
    end    
    stats = GetPluginVariableList("8a710e0783b431c06d61a54c")    
  -- state change
  elseif id == "f5b05e8826711cdb0d141939" then
    status = GetPluginVariableList("f5b05e8826711cdb0d141939")
  end -- if

  if old_state ~= status.state then
    waiting = -1
    resume_buff_loop ("playing_change", status.state)
  elseif new_stats then
    resume_buff_loop("newstats")
  elseif next(stats) and not (old_mana == nil or old_moves == nil) then
    pdebug("stats.mana: ", stats.mana, " old_mana: ", old_mana)
    if tonumber (stats.mana) > tonumber (old_mana) then
      low_mana = nil
      resume_buff_loop ("more_mana", stats.mana)
    elseif tonumber (stats.moves) > tonumber (old_moves) then
      low_moves = nil
      resume_buff_loop ("more_moves", stats.moves)
    end
  end -- something changed
end  -- OnPluginBroadcast

]]>
</script>

<!--  {spellheaders}  -->
<triggers>
 <trigger
   enabled="y"
   match="{spellheaders noprompt}"
   script="spellheaders_redirect"
   omit_from_output="y"
   name="start_spellheaders"
   sequence="100"
  >
  </trigger>
    
  <trigger
   enabled="n"
   match="*"
   script="spellheaders_redirect"
   name="multi_line_spellheaders"
   omit_from_output="y"
   sequence="10"
  >
  </trigger>  
</triggers>

<script>
<![CDATA[

-- spells redirector
function spellheaders_redirect (name, line, wildcards, styles)
  
  -- start of spells list? remove old ones
  if name == "start_spellheaders" then
    spells = {}
    spells_count = 0
    EnableTrigger ("multi_line_spellheaders", true)  -- capture subsequent lines
    return
  end -- if

  if line == "{/spellheaders}" then  
    EnableTrigger ("multi_line_spellheaders", false)  -- no more lines to go
    ColourNote ("green", "", "Loaded information about " .. spells_count .. " spells.")
    make_xrefs ()
    SaveState ()
    return
  end -- if
  
  local sn, name, target, duration, percent, recovery, skilltype = parse_spell_line (line)
  
  if not sn then return end
  
  spells [sn] = { 
      name = name:lower (),   -- name of spell
      target = target,        -- target code
      percent = percent,      -- percent known for this character
      recovery = recovery,    -- depends on which recovery
      skilltype = skilltype,  -- 1 = spell, 2 = skill
      } -- end spells table item
      
  spells_count = spells_count + 1
  
end -- function spellheaders_redirect 
]]>
</script>

<!--  {spellheaders spellups}  -->
<triggers>
 <trigger
   enabled="y"
   match="{spellheaders spellup noprompt}"
   script="spellups_redirect"
   omit_from_output="y"
   name="start_spellups"
   sequence="100"
  >
  </trigger>
    
  <trigger
   enabled="n"
   match="*"
   script="spellups_redirect"
   name="multi_line_spellups"
   omit_from_output="y"
   sequence="10"
  >
  </trigger>  
</triggers>

<script>
<![CDATA[

-- spells redirector
function spellups_redirect (name, line, wildcards, styles)
  
  -- start of spells list? remove old ones
  if name == "start_spellups" then
    -- mark all as not spellups for now
    for k, v in pairs (spells) do
      v.spellup = false
    end -- for
    
    EnableTrigger ("multi_line_spellups", true)  -- capture subsequent lines
    return
  end -- if

  if line == "{/spellheaders}" then  
    EnableTrigger ("multi_line_spellups", false)  -- no more lines to go
    have_slist = true  -- we now know all we need to
    make_xrefs ()
    SaveState ()
    resume_buff_loop("have_slist")
    return
  end -- if
  
  local sn, name, target, duration, percent, recovery, skilltype = parse_spell_line (line)
  
  if not sn then return end
  
  spells [sn] = { 
      name = name:lower (),   -- name of spell
      target = target,        -- target code
      percent = percent,      -- percent known for this character
      recovery = recovery,    -- depends on which recovery
      spellup = true,         -- this is a spellup
      skilltype = skilltype,  -- 1 = spell, 2 = skill
  } -- end spells table item
      
end -- function spellups_redirect 
]]>
</script>

<!--  {spellheaders affected}  -->
<triggers>
 <trigger
   enabled="y"
   match="{spellheaders affected noprompt}"
   script="affected_redirect"
   omit_from_output="y"
   name="start_affected"
   sequence="100"
  >
  </trigger>
    
  <trigger
   enabled="n"
   match="*"
   script="affected_redirect"
   name="multi_line_affected"
   omit_from_output="y"
   sequence="10"
  >
  </trigger>  
</triggers>

<script>
<![CDATA[

-- current affects redirector
function affected_redirect (name, line, wildcards, styles)
  
  -- start of affected list? remove old ones
  if name == "start_affected" then
    -- we must leave the "true" entries, because they won't be on the list possibly
    for k, v in pairs (current_buffs) do
      if v ~= true then
        current_buffs [k] = nil
      end -- if    
    end -- for
    check (EnableTrigger ("multi_line_affected", true))  -- capture subsequent lines
    return
  end -- if

  if line == "{/spellheaders}" then  
    check (EnableTrigger ("multi_line_affected", false))  -- no more lines to go
    return
  end -- if
  
  local sn, name, target, duration, percent, recovery, skilltype = parse_spell_line (line)

  if sn then
    current_buffs [sn] = os.time () + duration
  end  -- if
  
end -- function affected_redirect 


]]>
</script>

<!--  {spellheaders learned}  -->
<triggers>
<trigger
   enabled="y"
   match="{spellheaders learned noprompt}"
   script="learned_redirect"
   omit_from_output="y"
   name="start_learned"
   sequence="100"
  >
  </trigger>
    
  <trigger
   enabled="n"
   match="*"
   script="learned_redirect"
   name="multi_line_learned"
   omit_from_output="y"
   sequence="10"
  >
  </trigger>    
</triggers>

<script>
<![CDATA[

-- learned spells redirector
function learned_redirect (name, line, wildcards, styles)
  -- start of learned list? 
  if name == "start_learned" then
    -- reset learned amount
    for sn, v in pairs (spells) do
      v.percent = 0
    end -- for
    
    check (EnableTrigger ("multi_line_learned", true))  -- capture subsequent lines
    return
  end -- if

  if line == "{/spellheaders}" then  
    check (EnableTrigger ("multi_line_learned", false))  -- no more lines to go
    return
  end -- if
  
  local sn, name, target, duration, percent, recovery, skilltype = parse_spell_line (line)

  -- update our percent learned
  if sn then
    if spells[sn] == nil then
      Note("New spells have been added. Please type 'spellup refresh'")
    else
      spells [sn].percent = percent
    end
  end  -- if
  
end -- function learned_redirect 

]]>
</script>

<!--  {recoveries}  -->
<triggers>
<trigger
   enabled="y"
   match="{recoveries noprompt}"
   script="recoveries_redirect"
   omit_from_output="y"
   name="start_recoveries"
   sequence="100"
  >
  </trigger>
  
  <trigger
   enabled="n"
   match="*"
   script="recoveries_redirect"
   name="multi_line_recoveries"
   omit_from_output="y"
   sequence="10"
  >
  </trigger>  
</triggers>

<script>
<![CDATA[

-- cooldowns redirector
function recoveries_redirect (name, line, wildcards, styles)
  
  -- start of recoveries list? remove old ones
  if name == "start_recoveries" then
    recoveries = {}
    recoveries_count = 0
    EnableTrigger ("multi_line_recoveries", true)  -- capture subsequent lines
    return
  end -- if

  if line == "{/recoveries}" then  
    EnableTrigger ("multi_line_recoveries", false)  -- no more lines to go
    ColourNote ("green", "", "Loaded information about " .. recoveries_count .. " recoveries.")
    make_xrefs ()
    SaveState ()
    return
  end -- if
  
  local sn, name, duration = parse_recoveries_line (line)
  
  if not sn then
    return
  end -- not a valid spell name
 
  recoveries [sn] = { name = name  }
  recoveries_count = recoveries_count + 1
  
end -- function recoveries_redirect 

]]>
</script>

<!--  {recoveries affected}  -->
<triggers>
<trigger
   enabled="y"
   match="{recoveries affected noprompt}"
   script="recoveries_affected_redirect"
   omit_from_output="y"
   name="start_affected_recoveries"
   sequence="100"
  >
  </trigger>
  
  <trigger
   enabled="n"
   match="*"
   script="recoveries_affected_redirect"
   name="multi_line_affected_recoveries"
   omit_from_output="y"
   sequence="10"
  >
  </trigger>  
</triggers>

<script>
<![CDATA[

-- cooldowns redirector
function recoveries_affected_redirect (name, line, wildcards, styles)
  
  -- start of recoveries list? remove old ones
  if name == "start_affected_recoveries" then
    cooldowns = {}
    check (EnableTrigger ("multi_line_affected_recoveries", true))  -- capture subsequent lines
    return
  end -- if

  if line == "{/recoveries}" then  
    check (EnableTrigger ("multi_line_affected_recoveries", false))  -- no more lines to go
    return
  end -- if
  
  local sn, name, duration = parse_recoveries_line (line)
  
  if not sn then
    return
  end -- not a valid spell name
  
  cooldowns [sn] = os.time () + duration -- add to our table
  
end -- function recoveries_affected_redirect 

]]>
</script>

<!--  show buffs on us -->
<script>
<![CDATA[

-- note maximum spell name length is 24 at present

function show_spells (w, t, colour)
 
  for _, v in ipairs (t) do
     w:Hyperlink ("help " .. v.name, capitalize (v.name), "Help on '" .. capitalize (v.name) .. "'", 
                colour, "", 
                false)  -- not URL
          
     if tonumber (v.duration) then
       local time_to_go = v.duration - os.time ()
       local time_colour
       if time_to_go >= 180 then
         time_colour = "lime"
       elseif time_to_go >= 60 then
         time_colour = "yellow"
       else
         time_colour = "deeppink"
       end -- if
       
       w:ColourNote (time_colour, "", 
                    string.format ("%s %4s", string.rep (" ", 25 - #v.name), 
                                   convert_time (time_to_go)))
     else
       w:Note ""
     end      
  end -- for
end -- show_spells

function show_status (w)
  -- show our status
    
  w:ColourTell ("silver", "", "Position: " .. capitalize (stats.position_str or "unknown"))
  if status.statestring == "afk" then
    w:ColourTell ("cyan", "", " (AFK)")
  end -- AFK
  if low_mana then
    w:ColourTell ("yellow", "", " (Mana low)")
  end -- low_mana
  if low_moves then
    w:ColourTell ("yellow", "", " (Moves low)")
  end -- low_moves
  
  if not status.statestring == "active" then
    w:ColourTell ("cyan", "", " (Not playing)")
  end -- not playing
  
  if paused then
    w:ColourTell ("orangered", "", " (Paused)")
  end -- paused
  
  w:Note ""
end -- show_status

function show_brief_version (w) 
  local count, badcount = 0, 0
  for k in pairs (current_buffs) do
    if (spells [k].target == 1 or    -- combat
      spells [k].skilltype ~= 1) and spells[k].spellup == false then  -- not a spell
      badcount = badcount + 1
    else
      count = count + 1
    end
  end -- for

  w:Tell ("Spellups: " .. count)
  
  if badcount > 0 then
    w:ColourTell ("red", "", " Debuffs: " .. badcount)
  end -- if
  
  count = 0
  for k in pairs (cooldowns) do
    count = count + 1
  end -- for  

  w:Note (" Recoveries: " .. count)
  w:ColourTell ("olive", "", "Requested: ".. #wanted_buffs)
  
  local pending, oncooldown = 0, 0
  
  for _, v in ipairs (wanted_buffs) do
    if not current_buffs [v] then
      if cooldowns [spells [v].recovery] then
        oncooldown = oncooldown + 1
      else
        pending = pending + 1
      end -- if
    end -- not cast yet
  end -- for

  w:ColourTell ("gray", "", " Pending: " .. pending)
  w:ColourNote ("darkslateblue", "", " Cooldown: " .. oncooldown)
  
  show_status (w)
  
end -- show_brief_version

-- show our current state
function show_current_buffs (name, line, wildcards, styles)
  
  local w = get_a_world (affect_world, folder)
  if not w then
    return
  end
  
  if next (spells) == nil or 
     next (recoveries) == nil or
     stats.hp == nil then
    return
  end -- no spells known
  
  w:DeleteOutput ()

  if not IsConnected () then
    w:Note "Spellups will appear here."
    w:ColourNote ("silver", "", "Not connected to Aardwolf.")
    w:Note ""
    show_wanted (w)
    return
  end -- not connected
  
  if brief then
    show_brief_version (w)
    return
  end -- if brief
  
  if next (current_buffs) == nil then
    w:Note ("You are not affected by any spellups.")
    w:Note ""
  else
    local good = {}
    local bad = {}
    -- local ugly = {}  -- joke
  
    local count, badcount = 0, 0
    for k, v in pairs (current_buffs) do
      if (spells [k].target == 1 or    -- combat
        spells [k].skilltype ~= 1) and spells[k].spellup == false then  -- not a spell
        table.insert (bad, { name = spells [k].name, duration = v } )
        badcount = badcount + 1
      else
        table.insert (good, { name = spells [k].name, duration = v } )
        count = count + 1
      end -- if
    end -- for
    w:Tell ("Spellups affecting you: (" .. count .. ")")
    if badcount > 0 then
      w:ColourTell ("red", "", " / (" .. badcount .. ")")
    end -- if
    w:Note ":"
    
    w:Note ""
    table.sort (good, function (a, b) 
        if tonumber (a.duration) and tonumber (b.duration) then
          return a.duration < b.duration 
        end  -- if
        return a.name < b.name
        end )
    table.sort (bad, function (a, b) 
        if tonumber (a.duration) and tonumber (b.duration) then
          return a.duration < b.duration 
        end  -- if
        return a.name < b.name
    end )
            
    show_spells (w, good, "green")
    
    if #good > 0 and #bad > 0 then
      w:Note ""
    end
  
    show_spells (w, bad, "red")
  end -- if at least one spel
  
  w:Note ""
  
  if next (cooldowns) == nil then
    w:Note ("You have no spells on recovery.")
    w:Note ""
  else
    local cool = {}
    
    for k, v in pairs (cooldowns) do
      if (recoveries[k]) then
         table.insert (cool, { name = recoveries [k].name, duration = v } )
      else
         Note ("Warning - recovery " .. k .. " not known - please  type:  spellup refresh'")
        end -- if
    end -- for

    table.sort (cool, function (a, b) 
        if tonumber (a.duration) and tonumber (b.duration) then
          return a.duration < b.duration 
        end  -- if
        return a.name < b.name
    end )

    if #cool == 1 then
      w:Note ("Active recovery (1):")
    else
      w:Note ("Active recoveries (" .. #cool .. "):")
    end -- if    
    w:Note ""
    
    show_spells (w, cool, "yellow")
    
  end -- if at least one cooldown
  
  w:Note ""
    
  show_wanted (w)
  
  -- what is pending I wonder?
  
  pending = {}
  awaiting_cooldown = {}
  disabled = {}
  
  for _, v in ipairs (wanted_buffs) do
    if not current_buffs [v] then
      local text = capitalize(spells[v].name)
      if cooldowns [spells [v].recovery] then
        table.insert (awaiting_cooldown, text)
      else
        if isdisabled(v) then
          table.insert(disabled, text)
        else
          if isblocked(v) then
            text = text.." (Blocked)"        
          end -- blocked
          table.insert (pending, text)
        end -- disabled
      end -- if
    end -- not cast yet
  end -- for
 
  if #pending > 0 then
    w:ColourNote ("gray", "", "Pending (" .. #pending .. 
                  "): " .. table.concat (pending, ", "))
  end -- some pending
  if #disabled > 0 then
    w:ColourNote ("gray", "", "Disabled (" .. #disabled .. 
                  "): " .. table.concat (disabled, ", "))
  end -- some disabled
  if #awaiting_cooldown > 0 then
    w:ColourNote ("darkslateblue", "", "On cooldown (" .. #awaiting_cooldown .. 
                  "): " .. table.concat (awaiting_cooldown, ", "))
  end -- some pending

  show_status (w)
  
end -- function show_current_buffs 


]]>
</script>

<!--  show wanted buffs -->
<script>
<![CDATA[

function show_wanted (w)

  w = w or GetWorldById (GetWorldID ())
 
  if next (spells) == nil or 
    next (recoveries) == nil then
    return
  end -- no spells known
 
  if #wanted_buffs == 0 then
    w:ColourNote ("olive", "", "You have not requested any spellups.")
  else
    local buf_names = {}
    for _, sn in ipairs (wanted_buffs) do
      table.insert (buf_names, capitalize (spells [sn].name))
    end -- for
    w:ColourNote ("olive", "", "Requested (" .. #buf_names .. 
                  "): " .. table.concat (buf_names, ", "))
  end -- if
end -- show_wanted

]]>
</script>

<!--  configure wanted buffs -->
<aliases>
  <alias
   name="spellup"
   script="wanted_spellups"
   match="^spellups?\:?\s*((?<action>\+|\-)\s*)?(?<list>[\+\-A-Za-z0-9, :]+)?$"
   enabled="y"
   regexp="y"
   ignore_case="y"
   sequence="100"
  >
  </alias>
</aliases>

<script>
<![CDATA[
--[[
Spell targets are:
   0 : No target
   1 : Combat / Attack  
   2 : Spellup/cure - can cast on others.
   3 : Spellup/cure - self only.   
   4 : Objects.
--]]
   
function spellup_none (name, line, wildcards)
  if wildcards.action ~= "" then
    ColourNote ("red", "", "'" .. line .. "' does not make sense.")
    return true
  end -- if
  
  show_wanted ()
  wanted_buffs = {}
  ColourNote ("yellow", "", "All those above spellups REMOVED from spellups list.")
  return false  -- keep going
  
end -- spellup_none

function spellup_all (name, line, wildcards)
  if wildcards.action ~= "" then
    ColourNote ("red", "", "'" .. line .. "' does not make sense.")
    return
  end -- if
  wanted_buffs = {}
  
  for sn, v in pairs (spells) do
    if spells [sn].percent > 1 and
       spells [sn].spellup and
       spells [sn].skilltype == 1 then
       table.insert (wanted_buffs, sn) 
    end -- if possible to spellup this one
  end -- for
  ColourNote ("lime", "", "Set spellups list to all " .. #wanted_buffs .. " possible spellups.")
  if #wanted_buffs == 0 then
    ColourNote ("teal", "", "If 'spells spellup' show spells, you may need to 'spellup refresh'")
    ColourNote ("teal", "", "to reload your known list of spells.")
  end -- if none

  return false -- keep going
end -- spellup_all

function spellup_fast (name, line, wildcards)
  if paused then
    ColourNote ("green", "", "Spellups casting resumed.")
    paused = false
  end -- was paused

  -- better check what we can do
  local count = 0
  
  for _, v in ipairs (wanted_buffs) do
    if not current_buffs [v] then
      if not cooldowns [spells [v].recovery] then
        count = count + 1
      end -- if
    end -- not cast yet
  end -- for

  if count == 0 then
    ColourNote ("teal", "", "No pending spellups.")
  else
    ColourNote ("green", "", "Doing fast cast of " .. count .. " pending spellup(s).")
    resume_buff_loop ("fast", line)
  end
  
  return true  -- done
end -- spellup_fast

function spellup_pause (name, line, wildcards)
  if not paused then
    ColourNote ("orangered", "", "Spellups paused, type 'spellup resume' to continue.")
    paused = true
  else
    ColourNote ("orangered", "", "Spellups already paused, type 'spellup resume' to continue.")
  end -- if 
  return true  -- done
end -- spellup_pause

function spellup_resume (name, line, wildcards)
  if paused then
    waiting = -1
    ColourNote ("green", "", "Spellups casting resumed.")
    paused = false
  else
    ColourNote ("green", "", "Spellups already active.")
  end  -- if

  return false -- keep going
end -- spellup_resume

function spellup_help (name, line, wildcards)
  ColourNote ("teal", "", world.GetPluginInfo (world.GetPluginID (), 3))
  return true -- done  
end -- spellup_help

function spellup_refresh (name, line, wildcards)
  if status.statestring == "AFK" then
   ColourNote ("orangered", "", "Cannot refresh if you you are AFK.")
  elseif not status.statestring == "active" then
   ColourNote ("orangered", "", "Cannot refresh if you are not playing.")
  else
   ColourNote ("green", "", "Refreshing affected / learned / spellup list.")
   SendNoEcho "slist noprompt"
   SendNoEcho "slist affected noprompt"
   SendNoEcho "slist learned noprompt"
   SendNoEcho "slist spellup noprompt"
  end -- if      
  return true -- done    
end -- spellup_refresh

function spellup_brief (name, line, wildcards)
  ColourNote ("teal", "", "Brief spellup list will be shown. Type 'spellup full' to see more info.")
  brief = true
  show_current_buffs ()
  return true -- done  
end -- spellup_brief

function spellup_full (name, line, wildcards)
  ColourNote ("teal", "", "Full spellup list will be shown. Type 'spellup brief' to see less info.")
  brief = false
  show_current_buffs ()
  return true -- done  
end -- spellup_full

function spellup_other (name, line, wildcards)
  local who = string.match (wildcards.list, "^other (%a+)$")

  if not who then
    ColourNote ("red", "", "You did not specify a target!")
    return
  end

  for sn, v in pairs (spells) do
    if spells [sn].percent > 1 and   -- we know it
       spells [sn].spellup and       -- it is a spellup
       spells [sn].target == 2 and   -- can be cast on others
       spells [sn].skilltype == 1 then   -- spell not skill
         Send ("cast '" .. spells [sn].name .. "' " .. who)  -- cast it 
    end -- if possible to spellup this one
  end -- for
end -- spellup_other

function spellup_block(name, line, wildcards)
  local arguments = string.match (wildcards.list, "^block (.+)$")

  if not arguments then
    ColourNote ("red", "", "You did not specify any arguments!")
    ColourNote ("red", "", "Arguments:")
    ColourNote ("red", "", " +, - : <spell>, <spellthatblocks>")
    ColourNote ("red", "", " list: no arguments")    
    return true
  end

  fspace, tmp = string.find(arguments, " ")
  if not fspace then
    option = trim(arguments)
  else
    option = string.match(arguments, "^([%a\+-]+) ")
    spellstring = string.match(arguments, "(.+)$", fspace + 1)
  end

  if option == 'list' then
    lfound = false
    for sn, blockerlist in pairs(blockers) do
      for tmp, blocker in ipairs(blockers[sn]) do
        if not (blocker == disablednum) then
          lfound = true
          ColourNote("blue", "", capitalize(spells[sn].name) .. " is blocked by " .. capitalize(spells[blocker].name))
        end
      end
    end
    if not lfound then
      ColourNote("blue", "", "No spells are blocked")
    end     
    return true
  end  

  local tspells = utils.split(spellstring, ",")
  sncheck = tspells[1]
  blockcheck = tspells[2]
  sn, sninvalid = find_spellsn(sncheck)
  block, blinvalid = find_spellsn(blockcheck)
  if sninvalid or blinvalid then
    return false
  end
  if option == '-' then
    if remove_blocker(sn, block) then
        ColourNote("blue", "", "Removing " .. capitalize(spells[block].name) .. " as a blocker for " .. capitalize(spells[sn].name))
        return true
    end
  end
  if option == '+' then    
    if add_blocker(sn, block) then
        ColourNote("blue", "", "Adding " .. capitalize(spells[block].name) .. " as a blocker for " .. capitalize(spells[sn].name))
        return true
    end
  end    
  if #toptions == 0 then
    ColourNote ("red", "", "You did not specify any arguments!")
    return true
  end    

  return true
end -- spellup_block

function spellup_disable(name, line, wildcards)
  local arguments = string.match (wildcards.list, "^disable (.+)$")

  if not arguments then
    ColourNote ("red", "", "You did not specify any arguments!")
    ColourNote ("red", "", "Arguments:")
    ColourNote ("red", "", " +, - : <spelltodisable>")
    ColourNote ("red", "", " list: no arguments")    
    return true
  end

  fspace, tmp = string.find(arguments, " ")
  if not fspace then
    option = trim(arguments)
  else
    option = string.match(arguments, "^([%a\+-]+) ")
    spellstring = string.match(arguments, "(.+)$", fspace + 1)
  end

  if option == 'list' then
    lfound = false
    for sn, blockerlist in pairs(blockers) do
      for tmp, blocker in ipairs(blockers[sn]) do
        if blocker == disablednum then
          lfound = true
          ColourNote("blue", "", capitalize(spells[sn].name) .. " is disabled.")
        end
      end
    end
    if not lfound then
      ColourNote("blue", "", "No spells are disabled")
    end    
    return true
  end  

  sncheck = spellstring
  sn, sninvalid = find_spellsn(sncheck)
  if sninvalid then
    return false
  end
  if option == '-' then
    if remove_disabled(sn) then
        ColourNote("blue", "", capitalize(spells[sn].name) .. " is now enabled.")
        resume_buff_loop ("disable", line)
        return true
    end
  end
  if option == '+' then    
    if add_disabled(sn) then
        ColourNote("blue", "", capitalize(spells[sn].name) .. " is now disabled.")
        return true
    end
  end    
  if #toptions == 0 then
    ColourNote ("red", "", "You did not specify any arguments!")
    return true
  end    

  return true
end -- spellup_disable

function spellup_debug(name, line, wildcards)
   fdebug = not fdebug
   print("Debug is now set to: ", fdebug)
end -- spellup_debug

spellup_options = {
  none    = spellup_none,     --> no spellups
  all     = spellup_all,      --> set all possible
  fast    = spellup_fast,     --> quickly cast what you can
  pause   = spellup_pause,    --> pause doing spellups
  resume  = spellup_resume,   --> resume after pause
  help    = spellup_help,     --> show help 
  refresh = spellup_refresh,  --> request affected / learned / spellup from server
  brief   = spellup_brief,    --> show brief list
  full    = spellup_full,     --> show full list
  other   = spellup_other,    --> spellup other
  block   = spellup_block,    --> add a spell block
  disable = spellup_disable,  --> temporarily disable a single spell
  debug   = spellup_debug,    --> toggle the debug flag
  }
  
function wanted_spellups (name, line, wildcards)
  --tprint(wildcards)
  if wildcards.list then
  
    wildcards.list = trim (wildcards.list):lower ()

    toptions = utils.split(wildcards.list, " ")
    option = toptions[1]    
       
    local f = spellup_options [option]
    
    if f then
      if f (name, line, wildcards) then
        return
      end -- all done
    else
      
      -- before we change anything, make sure they all exist
      local invalid = false
      
      new_wanted_list = {}
      new_place_list = {}
      
      -- check names are valid
      for item in string.gmatch(wildcards.list, "[^,]+") do
        local place = nil
        colonplace, _ = string.find(item, ':')
        if colonplace and colonplace > 0 then
          tlist = utils.split(item, ':')
          item = tlist[1]
          place = tlist[2]
        end
        sn, invalid = find_spellsn(item)
               
        
        -- if found test it was ok
        if sn then
          name = spells [sn].name
          if spells [sn].percent == 0 then
            ColourNote ("red", "", "You have not learnt '" .. name .. "'.")
            invalid = true
          elseif spells [sn].percent == 1 then
            ColourNote ("red", "", "You have not practised '" .. name .. "'.")
            invalid = true
          elseif not spells [sn].spellup then
            ColourNote ("red", "", "Spell '" .. name .. "' is not a spellup.")
            invalid = true
          --elseif spells [sn].skilltype ~= 1 then
          --  ColourNote ("red", "", "Spell '" .. name .. "' is a skill, not a spell.")
          --  invalid = true
          else
            table.insert (new_wanted_list, sn)
            table.insert (new_place_list, tonumber(place))
          end -- if 
        end -- if number or name found
      end  -- for each spell in the list
      
      if invalid then
        ColourNote ("white", "red", "Spellups list contains or or more problems. List not changed.")
        show_wanted ()
        return
      end -- if
  
      -- add to existing list?
      if wildcards.action == "+" then
        for i, v in ipairs (new_wanted_list) do
          remove_spellup (v) -- don't have it there twice
          if new_place_list[i] then
            table.insert (wanted_buffs, new_place_list[i], v)
            ColourNote ("lime", "", capitalize (spells [v].name) .. " added to spellups list in position " .. new_place_list[i] .. ".")
          else
            table.insert (wanted_buffs, v)
            ColourNote ("lime", "", capitalize (spells [v].name) .. " added to end of spellups list.")
          end
        end -- for       
      -- remove from list?  
      elseif wildcards.action == "-" then
        for _, v in ipairs (new_wanted_list) do
          if remove_spellup (v) then
            ColourNote ("yellow", "", capitalize (spells [v].name) .. " removed from spellups list.")
          else
            ColourNote ("yellow", "", capitalize (spells [v].name) .. " was NOT in the spellups list.")
          end -- if 
        end -- for         
      else
        wanted_buffs = new_wanted_list
      end -- if
    end -- new list    
  end -- if wildcards

  show_wanted ()
  resume_buff_loop ("spellup", line)

  SaveState ()
end -- wanted_spellups
]]>
</script>

<!--  {affon}  -->
<triggers>
  <trigger
   enabled="y"
   omit_from_output="y"
   match="^\{affon\}(?<sn>\d+)\,(?<time>\d+)$"
   script="affect_on"
   regexp="y"
   sequence="100"
  >
  </trigger>
    
  <trigger
   enabled="y"
   omit_from_output="y"
   match="^\{affoff\}(?<sn>\d+)$"
   script="affect_off"
   regexp="y"
   sequence="100"
  >
  </trigger>
</triggers>

<script>
<![CDATA[

function affect_on (name, line, wildcards)
  local sn = tonumber (wildcards.sn)
  local time = tonumber (wildcards.time)
  
  cast_attempt_count [sn] = nil
  failed_attempt_count [sn] = nil
  skip [sn] = nil
  current_buffs [sn] = os.time () + time
  if waiting == sn then
    pdebug("changing waiting to -1")
    waiting = -1
    resume_buff_loop ("affecton", line)
  end

  -- success? must have had required mana/moves
  low_mana = nil
  low_moves = nil

  if not spells [sn] then return end
  
  if spells [sn].target == 1 or    -- combat
    spells [sn].skilltype ~= 1 then  -- not a spell
      BroadcastPlugin (2, spells [sn].name)  -- notify we got bad spell
    else
      BroadcastPlugin (1, spells [sn].name)  -- notify we got good spell
  end -- if bad spell
  
end -- affect_on

function affect_off (name, line, wildcards)
  local sn = tonumber (wildcards.sn)
  
  cast_attempt_count [sn] = nil
  failed_attempt_count [sn] = nil
  current_buffs [sn] = nil

  -- with a spell worn off, retry all skipped spells
  for k in pairs (skip) do
    skip [k] = nil
    cast_attempt_count [k] = nil
  end -- for
  
  resume_buff_loop ("affectoff", line)
  
  if not spells [sn] then return end
  
  if spells [sn].target == 1 or    -- combat
     spells [sn].skilltype ~= 1 then  -- not a spell
       BroadcastPlugin (4, spells [sn].name)  -- notify we lost bad spell
     else
       BroadcastPlugin (3, spells [sn].name)  -- notify we lost good spell
  end -- if bad spell
    
end -- affect_off
]]>
</script>

<!--  {recon}  -->
<triggers>
 <trigger
   enabled="y"
   omit_from_output="y"
   match="^\{recon\}(?<sn>\d+)\,(?<time>\d+)$"
   script="recovery_on"
   regexp="y"
   sequence="100"
  >
  </trigger>
    
  <trigger
   enabled="y"
   omit_from_output="y"
   match="^\{recoff\}(?<sn>\d+)$"
   script="recovery_off"
   regexp="y"
   sequence="100"
  >
  </trigger>
</triggers>

<script>
<![CDATA[
  
function recovery_on (name, line, wildcards)
  local sn = tonumber (wildcards.sn)
  local time = tonumber (wildcards.time)
  
  cooldowns [sn] = os.time () + time
  resume_buff_loop ("recoveryon", line)
end -- recovery_on

function recovery_off (name, line, wildcards)
  local sn = tonumber (wildcards.sn)
  
  cooldowns [sn] = nil
  resume_buff_loop ("recoveryoff", line)
end -- recovery_off
]]>
</script>

<!--  {skillgain}  -->
<triggers>
 <trigger
   enabled="y"
   omit_from_output="y"
   match="^\{skillgain\}(?<sn>\d+)\,(?<percent>\d+)$"
   script="skillgain"
   regexp="y"
   sequence="100"
  >
  </trigger>
</triggers>

<script>
<![CDATA[    

-- we gained a skill
function skillgain (name, line, wildcards)
  local sn = tonumber (wildcards.sn)
  local percent = tonumber (wildcards.percent)

  spells [sn].percent = percent
  if spells [sn] then
    ColourNote ("yellow", "", string.format ("Your proficiency at %s is now %i%%.",
                spells [sn].name, percent))
  end --  if exists                
  resume_buff_loop ("skillgain", line)
end -- skillgain
]]>
</script>

<!--  heartbeat timer  -->
<timers>
  <timer name="affects_timer"
         enabled="n" 
         second="5.00" 
         script="heartbeat"
         >
  </timer>
 
 <timer name="display_timer"
         enabled="y" 
         second="1.00" 
         script="display_timer"
         active_closed="y"
         >
  </timer>
</timers>

<script>
<![CDATA[  

-- kick the loop along, in case nothing happening
function heartbeat (timername)
  resume_buff_loop "timer"
end -- heartbeat 

-- redisplay our status every second
function display_timer (timername)
  show_current_buffs ()
end -- heartbeat 

]]>
</script>

<!--  {sfail}  -->
<triggers>
  <trigger
   enabled="y"
   omit_from_output="y"
   match="^\{sfail\}(?<sn>\-?\d+)\,(?<target>\d+)\,(?<reason>\d+)\,(?<recovery>\-?\d+)$"
   script="spell_failure"
   regexp="y"
   sequence="100"
  >
  </trigger>
  
  <trigger
   custom_colour="2"
   enabled="y"
   match="You do not know a '*' spell."
   script="unknown_spell"
   sequence="100"
  >
  </trigger>
</triggers>

<script>
<![CDATA[  
--[[
   1: Regular fail
   2: Already affected
   3: Recovery blocked it - 4th value is recovery or -1 (notarg)
   4: Not enough mana
   5: Nocast room.  (notarg)
   6: Can't concentrate (in combat) (notarg)
   7: Spell disabled (notarg)
   8: Spell not known (notarg)
   9: Tried to cast self-only spell on another.
  10: Resting / sleeping
  11: Other.
  12: Not enough moves (some skills require moves not mana).
  --]]
  
function spell_failure (name, line, wildcards)
  local sn = tonumber (wildcards.sn)
  local target = tonumber (wildcards.target)
  local reason = tonumber (wildcards.reason)
  local recovery = tonumber (wildcards.recovery)
 
  pdebug ("spell failure: sn=", sn, "target=", target, "reason=", reason, "recovery=", recovery)
  
  -- REGULAR FAIL
  if reason == 1 then --  Regular fail - recast
     failed_attempt_count [sn] = (failed_attempt_count [sn] or 0) + 1
     if failed_attempt_count [sn] > MAX_RETRIES then
       if remove_spellup (sn) then
         ColourNote ("red", "", "Failed to cast '" .. capitalize (spells [sn].name) .. 
                     "' "  .. MAX_RETRIES .. " times. Removing from spellup list.")
       end -- if we had it in the first place
     end -- if
     
  -- ALREADY AFFECTED
  elseif reason == 2 then  -- Already affected - note that
     current_buffs [sn] =  current_buffs [sn] or true  -- maybe it just got cast

  -- ON COOLDOWN
  elseif reason == 3 then  -- On recovery
     cooldowns [recovery] =  cooldowns [recovery] or true  -- maybe it just got cast
     
  -- NOT ENOUGH MANA
  elseif reason == 4 then  -- No mana, skip it
     low_mana = tonumber (stats.mana)

  -- SPELL DISABLED
  elseif reason == 7 then  -- OooO  - spell disabled
     ColourNote ("red", "", "Spell '" .. capitalize (spells [sn].name) .. 
                 "' disabled. Removing from spellup list.")
     remove_spellup (sn)
     
  -- SPELL NOT KNOWN
  elseif reason == 8 then  -- Not known - maybe remorted or something
     if sn ~= -1 and spells [sn] then
       if remove_spellup (sn) then
         ColourNote ("red", "", "Unknown spell '" .. capitalize (spells [sn].name) .. 
                     "'. Removing from spellup list.")
       end -- if in list
     end -- if known

  -- OTHER REASON
  elseif reason == 11 then  -- Other - who knows?
     if sn ~= -1 and spells [sn] then
       if remove_spellup (sn) then
         ColourNote ("red", "", "Problem casting '" .. capitalize (spells [sn].name) .. 
                     "'. Removing from spellup list.")
       end -- if in list
     end -- if known
                
  -- NOT ENOUGH MOVES
  elseif reason == 12 then  -- No moves, skip it
     low_moves = tonumber (stats.moves)
     
  end -- if 
  
  if waiting == -1 or (waiting > 0 and waiting == sn) then
    resume_buff_loop ("sfail", line)
  end
end -- spell_failure


-- eg. You do not know a 'berserk' spell.
function unknown_spell (name, line, wildcards)
  local sn = spells_xref [wildcards [1]:lower ()]
  
  if sn then
    if remove_spellup (sn) then
      ColourNote ("red", "", "Unknown spell '" .. capitalize (spells [sn].name) .. 
                   "'. Removing from spellup list.")
    end -- in list in the first place    
  end -- if
    
end -- unknown_spell
]]>
</script>

<!--  MAIN LOOP  -->
<script>
<![CDATA[
--[[
positions:
   0 = dead
   1 = sleeping
   2 = resting
   3 = sitting  
   4 = fighting
   5 = standing
--]]

-- called by coroutine to attempt one spellup
function try_to_buff_us (reason)

  pdebug("try_to_buff_us: ", reason)
  local fast = reason == "fast"

  -- need a certain minimal amount of information to proceed
  if next (spells) == nil or 
     next (recoveries) == nil or
     stats.hp == nil then
    pdebug("return because we don't have enough info")
    return
  end -- tables not set up  

 -- proceed through wanted ones until we find one we can cast
  for _, sn in ipairs (wanted_buffs) do
  
    -- uh oh, they want a non-existant spell
    if not spells [sn] then
      return
    end -- spell doesn't exist, strange

    local active = current_buffs [sn]  -- is it already active?
    local cooldown = cooldowns [spells [sn].recovery]  -- is its cooldown active?
    
    --pdebug("sn=", sn, "name=", spells [sn].name, "active=", active)

    -- recast this one if it is not active, and the cooldown has expired
    if not active and       -- not already on us
       not cooldown and     -- is not cooling down
       not isblocked(sn) and
       not isdisabled(sn) and
       (fast or not skip [sn]) then   -- not skipping because we couldn't cast
      cast_attempt_count [sn] = (cast_attempt_count [sn] or 0) + 1
      if cast_attempt_count [sn] > MAX_RETRIES then
        skip [sn] = true
        ColourNote ("yellow", "", "Too many attempts to cast '" .. capitalize (spells [sn].name) ..
                    "', skipping it for now.")
      else
        if spells[sn].skilltype == 1 then
          --Send ("kill fom")
          Send ("cast '" .. spells [sn].name .. "'")
        else
          words = justWords(spells[sn].name)
          Send (words[1])
        end
        pdebug("changing waiting to ", sn)
        waiting = sn
        last_cast_time = os.time ()
        spells [sn].last_cast = os.time ()
        if not fast then
          return  -- stop so we cast in correct order
        end -- unless they want the lot
      end -- if
    end -- not currently on us
  end -- for

end -- try_to_buff_us

--- Called by coroutine to see if we need to request the affected list
function check_we_know_times ()
  local ok = true

  for sn, time in pairs (current_buffs) do
    if not tonumber (time) then
      ok = false
    end -- if
  end -- for
  
  if ok then
    return
  end -- all times known

  if os.time () > (last_time_check + 30) then 
    SendNoEcho "slist affected noprompt"
    last_time_check = os.time ()
  end -- if 
  
end -- check_we_know_times

--- COROUTINE HERE ----
function buff_loop (reason, other_reason)
  have_slist = false
  
  last_time_check = last_time_check or (os.time () - 60)
  last_cast_time = os.time ()
  
  low_mana = nil
  low_moves = nil

  while reason ~= "disconnect" do
 --   pdebug ("coroutine kicked, reason:", reason, other_reason)

    -- early on, we need to request lists of known spells etc.
    if status.statestring == "active" then    
      if have_slist then
        check_we_know_times ()
      else
        if os.time () > (last_time_check + 45) then 
          if next (spells) == nil then
            SendNoEcho "slist noprompt"
          end -- if no spells known      
          SendNoEcho "slist affected noprompt"
          SendNoEcho "slist learned noprompt"
          SendNoEcho "slist spellup noprompt"
          last_time_check = os.time ()
        end -- if 
      end
    end -- if playing

    if status.statestring == "active" and    -- need to be active
       not low_mana and      -- not recently out of mana
       not low_moves and     -- not recently out of moves
       reason ~= "recoveryon" and  -- recovery on is usually followed by {affon}
       have_slist and        -- have received list of spells
       not paused and        -- they haven't paused us
       (reason == "affecton" or reason == "fast" or reason == "sfail" or last_cast_time ~= os.time ()) then  -- only once a second, or we thrash
      try_to_buff_us (reason)    
    end -- OK to cast
           
    ResetTimer ("affects_timer")  -- heartbeat not needed for a while yet
    reason, other_reason = coroutine.yield ()  
  end -- main loop

end -- buff_loop
]]>
</script>
</muclient>
