Example #1
0
class Plugin(AardwolfBasePlugin):
  """
  a plugin manage info about spells and skills
  """
  def __init__(self, *args, **kwargs):
    """
    initialize the instance
    """
    AardwolfBasePlugin.__init__(self, *args, **kwargs)
    self.saveskillfile = os.path.join(self.savedir, 'skills.txt')
    self.skills = PersistentDict(self.saveskillfile, 'c')
    self.skillsnamelookup = {}
    for i in self.skills:
      self.skillsnamelookup[self.skills[i]['name']] = i

    self.saverecovfile = os.path.join(self.savedir, 'recoveries.txt')
    self.recoveries = PersistentDict(self.saverecovfile, 'c')
    self.recoveriesnamelookup = {}
    for i in self.recoveries:
      self.recoveriesnamelookup[self.recoveries[i]['name']] = i

    self.current = ''
    self.isuptodatef = False

    self.cmdqueue = None

    self.api.get('dependency.add')('cmdq')

    self.api.get('api.add')('gets', self.api_getskill)
    self.api.get('api.add')('isspellup', self.api_isspellup)
    self.api.get('api.add')('getspellups', self.api_getspellups)
    self.api.get('api.add')('sendcmd', self.api_sendcmd)
    self.api.get('api.add')('isaffected', self.api_isaffected)
    self.api.get('api.add')('isblockedbyrecovery',
                                        self.api_isblockedbyrecovery)
    self.api.get('api.add')('ispracticed', self.api_ispracticed)
    self.api.get('api.add')('canuse', self.api_canuse)
    self.api.get('api.add')('isuptodate', self.api_isuptodate)
    self.api.get('api.add')('isbad', self.api_isbad)

  def load(self):
    """
    load the plugins
    """
    AardwolfBasePlugin.load(self)

    self.api.get('send.msg')('running load function of skills')

    parser = argparse.ArgumentParser(add_help=False,
                 description='refresh skills and spells')
    self.api.get('commands.add')('refresh', self.cmd_refresh,
                                 parser=parser)

    parser = argparse.ArgumentParser(add_help=False,
                 description='lookup skill or spell by name or sn')
    parser.add_argument('skill', help='the skill to lookup',
                        default='', nargs='?')
    self.api.get('commands.add')('lu', self.cmd_lu,
                                 parser=parser)

    self.api.get('triggers.add')('spellh_noprompt',
            "^\{spellheaders noprompt\}$",
            group='slist', enabled=False, omit=True)
    self.api.get('triggers.add')('spellh_spellup_noprompt',
            "^\{spellheaders spellup noprompt\}$",
            group='slist', enabled=False, omit=True)
    self.api.get('triggers.add')('spellh_affected_noprompt',
            "^\{spellheaders affected noprompt\}$",
            group='slist', enabled=False, omit=True)
    self.api.get('triggers.add')('spellh_spellline',
            "^(?P<sn>\d+),(?P<name>.+),(?P<target>\d+)," \
              "(?P<duration>\d+),(?P<pct>\d+),(?P<rcvy>-?\d+),(?P<type>\d+)$",
            group='spellhead', enabled=False, omit=True)
    self.api.get('triggers.add')('spellh_end_noprompt',
            "^\{/spellheaders\}$",
            group='spellhead', enabled=False, omit=True)
    self.api.get('triggers.add')('affoff',
            "^\{affoff\}(?P<sn>\d+)$")
    self.api.get('triggers.add')('affon',
            "^\{affon\}(?P<sn>\d+),(?P<duration>\d+)$")
    self.api.get('triggers.add')('recov_noprompt',
            "^\{recoveries noprompt\}$",
            group='slist', enabled=False, omit=True)
    self.api.get('triggers.add')('recov_affected_noprompt',
            "^\{recoveries affected noprompt\}$",
            group='slist', enabled=False, omit=True)
    self.api.get('triggers.add')('spellh_recovline',
            "^(?P<sn>\d+),(?P<name>.+),(?P<duration>\d+)$",
            group='recoveries', enabled=False, omit=True)
    self.api.get('triggers.add')('recov_end_noprompt',
            "^\{/recoveries\}$",
            group='recoveries', enabled=False, omit=True)
    self.api.get('triggers.add')('recoff',
            "^\{recoff\}(?P<sn>\d+)$")
    self.api.get('triggers.add')('recon',
            "^\{recon\}(?P<sn>\d+),(?P<duration>\d+)$")
    self.api.get('triggers.add')('skillgain',
            "^\{skillgain\}(?P<sn>\d+),(?P<percent>\d+)$")
    self.api.get('triggers.add')('skillfail',
            "^\{sfail\}(?P<sn>\d+),(?P<target>\d+)," \
              "(?P<reason>\d+),(?P<recovery>-?\d+)$")

    self.api.get('events.register')('trigger_spellh_noprompt',
                                    self.skillstart)
    self.api.get('events.register')('trigger_spellh_spellup_noprompt',
                                    self.skillstart)
    self.api.get('events.register')('trigger_spellh_affected_noprompt',
                                    self.skillstart)
    self.api.get('events.register')('trigger_spellh_spellline',
                                    self.skillline)
    self.api.get('events.register')('trigger_spellh_end_noprompt',
                                    self.skillend)
    self.api.get('events.register')('trigger_affoff', self.affoff)
    self.api.get('events.register')('trigger_affon', self.affon)
    self.api.get('events.register')('trigger_recov_noprompt',
                                    self.recovstart)
    self.api.get('events.register')('trigger_recov_affected_noprompt',
                                    self.recovstart)
    self.api.get('events.register')('trigger_spellh_recovline',
                                    self.recovline)
    self.api.get('events.register')('trigger_recov_end_noprompt',
                                    self.recovend)
    self.api.get('events.register')('trigger_recoff', self.recoff)
    self.api.get('events.register')('trigger_recon', self.recon)

    self.api.get('events.register')('trigger_skillgain', self.skillgain)
    self.api.get('events.register')('trigger_skillfail', self.skillfail)

    self.api.get('events.register')('GMCP:char.status', self.checkskills)

    self.api.get('events.register')('aard_level_tier', self.cmd_refresh)
    self.api.get('events.register')('aard_level_remort', self.cmd_refresh)


    self.cmdqueue = self.api.get('cmdq.baseclass')()(self)
    self.cmdqueue.addcmdtype('slist', 'slist', "^slist\s*(.*)$",
                       beforef=self.slistbefore, afterf=self.slistafter)

    self.checkskills()

  def slistbefore(self):
    """
    stuff to do before doing slist command
    """
    self.api.get('triggers.togglegroup')('slist', True)

  def slistafter(self):
    """
    stuff to do after doing slist command
    """
    self.savestate()
    self.api.get('triggers.togglegroup')('slist', False)

  def afterfirstactive(self, _=None):
    """
    do something on connect
    """
    AardwolfBasePlugin.afterfirstactive(self)
    self.checkskills()

  # check if the spells/skills list is up to date
  def api_isuptodate(self):
    """
    return True if we have seen affected or all spells refresh
    """
    return self.isuptodatef

  def cmd_lu(self, args):
    """
    cmd to lookup a spell
    """
    msg = []
    skill = self.api.get('skills.gets')(args['skill'])
    if skill:
      msg.append('%-8s : %s' % ('SN', skill['sn']))
      msg.append('%-8s : %s' % ('Name', skill['name']))
      msg.append('%-8s : %s' % ('Percent', skill['percent']))
      if skill['duration'] > 0:
        msg.append('%-8s : %s' % ('Duration',
            self.api.get('utils.timedeltatostring')(time.time(),
              skill['duration'])))
      msg.append('%-8s : %s' % ('Target', skill['target']))
      msg.append('%-8s : %s' % ('Spellup', skill['spellup']))
      msg.append('%-8s : %s' % ('Type', skill['type']))
      if skill['recovery']:
        recov = skill['recovery']
        if recov['duration'] > 0:
          duration =  self.api.get('utils.timedeltatostring')(time.time(),
              recov['duration'])
          msg.append('%-8s : %s (%s)' % ('Recovery',
                                      recov['name'], duration))
        else:
          msg.append('%-8s : %s' % ('Recovery', recov['name']))
    else:
      msg.append('Could not find: %s' % args['skill'])

    return True, msg

  def cmd_refresh(self, args):
    """
    refresh spells and skills
    """
    self.skills.clear()
    self.recoveries.clear()
    self.cmdqueue.addtoqueue('slist', 'noprompt')
    self.cmdqueue.addtoqueue('slist', 'spellup noprompt')
    msg = ['Refreshing spells and skills']
    return True, msg

  def checkskills(self, _=None):
    """
    check to see if we have spells
    """
    state = self.api.get('GMCP.getv')('char.status.state')
    if state == 3:
      self.api.get('send.msg')('refreshing skills')
      self.api.get('events.unregister')('GMCP:char.status', self.checkskills)
      self.api.get('A102.toggle')('SPELLUPTAGS', True)
      self.api.get('A102.toggle')('SKILLGAINTAGS', True)
      self.api.get('A102.toggle')('QUIETTAGS', False)
      if len(self.skills) == 0:
        self.cmd_refresh({})
      else:
        self.resetskills()
        self.cmdqueue.addtoqueue('slist', 'affected noprompt')

  def resetskills(self):
    """
    reset the skills
    """
    for i in self.skills:
      self.skills[i]['duration'] = 0
    for i in self.recoveries:
      self.recoveries[i]['duration'] = 0

  def skillgain(self, args):
    """
    handle a skillgain tag
    """
    spellnum = int(args['sn'])
    pct = int(args['percent'])
    if spellnum in self.skills:
      self.skills[spellnum]['percent'] = pct
      self.api.get('events.eraise')('aard_skill_gain',
                                    {'sn':spellnum, 'percent':pct})

  def skillfail(self, args):
    """
    raise an event when we fail a skill/spell
    """
    spellnum = int(args['sn'])
    reason = FAILREASON[int(args['reason'])]
    ndict = {'sn':spellnum, 'reason':reason,
            'target':FAILTARG[int(args['target'])],
            'recovery':int(args['recovery'])}
    if reason == 'dontknow' and self.skills[spellnum]['percent'] > 0:
      self.api.get('send.msg')('refreshing spells because of an unlearned spell')
      self.cmd_refresh({})
    self.api.get('send.msg')('raising skillfail: %s' % ndict)
    self.api.get('events.eraise')('skill_fail_%s' % args['sn'], ndict)
    self.api.get('events.eraise')('skill_fail', ndict)

  def affoff(self, args):
    """
    set the affect to off for spell that wears off
    """
    spellnum = int(args['sn'])
    if spellnum in self.skills:
      self.skills[spellnum]['duration'] = 0
      self.savestate()
      self.api.get('events.eraise')('aard_skill_affoff_%s' % spellnum,
                                              {'sn':spellnum})
      self.api.get('events.eraise')('aard_skill_affoff', {'sn':spellnum})

  def affon(self, args):
    """
    set the spell's duration when we see an affon
    """
    spellnum = int(args['sn'])
    duration = int(args['duration'])
    if spellnum in self.skills:
      self.skills[spellnum]['duration'] = time.mktime(time.localtime()) + \
                                                        duration
      self.savestate()
      self.api.get('events.eraise')('aard_skill_affon_%s' % spellnum,
                                              {'sn':spellnum,
                              'duration':self.skills[spellnum]['duration']})
      self.api.get('events.eraise')('aard_skill_affon', {'sn':spellnum,
                              'duration':self.skills[spellnum]['duration']})

  def recovstart(self, args):
    """
    show that the trigger fired
    """
    if 'triggername' in args \
        and args['triggername'] == 'trigger_recov_affected_noprompt':
      self.current = 'affected'
    else:
      self.current = ''
    self.api.get('triggers.togglegroup')('recoveries', True)

  def recovline(self, args):
    """
    parse a recovery line
    """
    spellnum = int(args['sn'])
    name = args['name']
    if int(args['duration']) != 0:
      duration = time.mktime(time.localtime()) + int(args['duration'])
    else:
      duration = 0

    if not (spellnum in self.recoveries):
      self.recoveries[spellnum] = {}

    self.recoveries[spellnum]['name'] = name
    self.recoveries[spellnum]['duration'] = duration
    self.recoveries[spellnum]['sn'] = spellnum

    self.recoveriesnamelookup[name] = spellnum

  def recovend(self, args):
    """
    reset current when seeing a spellheaders ending
    """
    self.api.get('triggers.togglegroup')('recoveries', False)
    if self.current == '' or self.current == 'affected':
      self.isuptodatef = True
      self.api.get('send.msg')('sending skills_affected_update')
      self.api.get('events.eraise')('skills_affected_update', {})
    self.cmdqueue.cmddone('slist')

  def recoff(self, args):
    """
    set the affect to off for spell that wears off
    """
    spellnum = int(args['sn'])
    if spellnum in self.recoveries:
      self.recoveries[spellnum]['duration'] = 0
      self.savestate()
      self.api.get('events.eraise')('aard_skill_recoff', {'sn':spellnum})

  def recon(self, args):
    """
    set the spell's duration when we see an affon
    """
    spellnum = int(args['sn'])
    duration = int(args['duration'])
    if spellnum in self.recoveries:
      self.recoveries[spellnum]['duration'] = \
                        time.mktime(time.localtime()) + duration
      self.savestate()
      self.api.get('events.eraise')('aard_skill_recon', {'sn':spellnum,
                            'duration':self.recoveries[spellnum]['duration']})

  def skillstart(self, args):
    """
    show that the trigger fired
    """
    if 'triggername' in args \
        and args['triggername'] == 'spellh_spellup_noprompt':
      self.current = 'spellup'
    elif 'triggername' in args \
        and args['triggername'] == 'spellh_affected_noprompt':
      self.current = 'affected'
    else:
      self.current = ''
    self.api.get('triggers.togglegroup')('spellhead', True)

  def skillline(self, args):
    """
    parse spell lines
    """
    spellnum = int(args['sn'])
    name = args['name']
    target = int(args['target'])
    if int(args['duration']) != 0:
      duration = time.mktime(time.localtime()) + int(args['duration'])
    else:
      duration = 0
    percent = int(args['pct'])
    recovery = int(args['rcvy'])
    stype = int(args['type'])

    if not (spellnum in self.skills):
      self.skills[spellnum] = {}

    self.skills[spellnum]['name'] = name
    self.skills[spellnum]['target'] = TARGET[target]
    self.skills[spellnum]['duration'] = duration
    self.skills[spellnum]['percent'] = percent
    self.skills[spellnum]['recovery'] = recovery
    self.skills[spellnum]['type'] = STYPE[stype]
    self.skills[spellnum]['sn'] = spellnum
    if not ('spellup' in self.skills[spellnum]):
      self.skills[spellnum]['spellup'] = False
    if self.current == 'spellup':
      self.skills[spellnum]['spellup'] = True

    self.skillsnamelookup[name] = spellnum

  def skillend(self, args):
    """
    reset current when seeing a spellheaders ending
    """
    self.api.get('triggers.togglegroup')('spellhead', False)
    self.savestate()
    if self.current:
      evname = 'aard_skill_ref_%s' % self.current
    else:
      evname = 'aard_skill_ref'
    self.api.get('events.eraise')(evname, {})
    self.current = ''

  # get a spell/skill by number
  def api_getskill(self, tsn):
    """
    get a skill
    """
    #self.api.get('send.msg')('looking for %s' % tsn)
    spellnum = -1
    name = tsn
    try:
      spellnum = int(tsn)
    except ValueError:
      pass

    tskill = None
    if spellnum >= 1:
      #self.api.get('send.msg')('%s >= 0' % spellnum)
      if spellnum in self.skills:
        #self.api.get('send.msg')('found spellnum')
        tskill = copy.deepcopy(self.skills[spellnum])
        #tskill = self.skills[spellnum]
      else:
        self.api.get('send.msg')('did not find skill for %s' % spellnum)

    if not tskill and name:
      #self.api.get('send.msg')('trying name')
      tlist = self.api.get('utils.checklistformatch')(name,
                                                self.skillsnamelookup.keys())
      if len(tlist) == 1:
        tskill = copy.deepcopy(self.skills[self.skillsnamelookup[tlist[0]]])

    if tskill:
      if tskill['recovery'] and tskill['recovery'] != -1:
        tskill['recovery'] = copy.deepcopy(self.recoveries[tskill['recovery']])
      else:
        tskill['recovery'] = None

    return tskill

  # send the command to active a skill/spell
  def api_sendcmd(self, spellnum):
    """
    send the command to activate a skill/spell
    """
    skill = self.api.get('skills.gets')(spellnum)
    if skill:
      if skill['type'] == 'spell':
        self.api.get('send.msg')('casting %s' % skill['name'])
        self.api.get('send.execute')('cast %s' % skill['sn'])
      else:
        name = skill['name'].split()[0]
        self.api.get('send.msg')('sending skill %s' % skill['name'])
        self.api.get('send.execute')(name)

  # check if a skill/spell can be used
  def api_canuse(self, spellnum):
    """
    return True if the spell can be used
    """
    if self.api.get('skills.isaffected')(spellnum) \
        or self.api.get('skills.isblockedbyrecovery')(spellnum) \
        or not self.api.get('skills.ispracticed')(spellnum):
      return False

    return True

  # check if a skill/spell is a spellup
  def api_isspellup(self, spellnum):
    """
    return True for a spellup, else return False
    """
    spellnum = int(spellnum)
    if spellnum in self.skills:
      return self.skills[spellnum]['spellup']

    return False

  # check if a skill/spell is bad
  def api_isbad(self, spellnum):
    """
    return True for a bad spell, False for a good spell
    """
    skill = self.api.get('skill.gets')(spellnum)
    if (skill['target'] == 'attack' or skill['target'] == 'special') and \
          not skill['spellup']:
      return True

    return False

  # check if a skill/spell is active
  def api_isaffected(self, spellnum):
    """
    return True for a spellup, else return False
    """
    skill = self.api.get('skills.gets')(spellnum)
    if skill:
      return skill['duration'] > 0

    return False

  # check if a skill/spell is blocked by a recovery
  def api_isblockedbyrecovery(self, spellnum):
    """
    check to see if a spell/skill is blocked by a recovery
    """
    skill = self.api.get('skills.gets')(spellnum)
    if skill:
      if 'recovery' in skill and skill['recovery'] and \
          skill['recovery']['duration'] > 0:
        return True

    return False

  # check if a skill/spell is practiced
  def api_ispracticed(self, spellnum):
    """
    is the spell learned
    """
    skill = self.api.get('skills.gets')(spellnum)
    if skill:
      if skill['percent'] > 10:
        return True

    return False

  # get the list of spellup spells/skills
  def api_getspellups(self):
    """
    return a list of spellup spells
    """
    sus = [x for x in self.skills.values() if x['spellup']]
    return sus

  def savestate(self):
    """
    save states
    """
    AardwolfBasePlugin.savestate(self)
    self.skills.sync()
    self.recoveries.sync()
Example #2
0
class Plugin(BasePlugin):
  """
  a plugin for user actions
  """
  def __init__(self, *args, **kwargs):
    """
    initialize the instance
    """
    BasePlugin.__init__(self, *args, **kwargs)

    self.canreload = True

    self.regexlookup = {}
    self.actiongroups = {}
    self.compiledregex = {}
    self.sessionhits = {}

    self.saveactionsfile = os.path.join(self.savedir, 'actions.txt')
    self.actions = PersistentDict(self.saveactionsfile, 'c')

  def load(self):
    """
    load the plugin
    """
    BasePlugin.load(self)

    self.api('setting.add')('nextnum', 0, int,
                            'the number of the next action added',
                            readonly=True)

    parser = argp.ArgumentParser(add_help=False,
                                 description='add a action')
    parser.add_argument('regex',
                        help='the regex to match',
                        default='',
                        nargs='?')
    parser.add_argument('action',
                        help='the action to take',
                        default='',
                        nargs='?')
    parser.add_argument('send',
                        help='where to send the action',
                        default='execute',
                        nargs='?',
                        choices=self.api('api.getchildren')('send'))
    parser.add_argument('-c',
                        "--color",
                        help="match colors (@@colors)",
                        action="store_true")
    parser.add_argument('-d',
                        "--disable",
                        help="disable the action",
                        action="store_true")
    parser.add_argument('-g',
                        "--group",
                        help="the action group",
                        default="")
    parser.add_argument('-o',
                        "--overwrite",
                        help="overwrite an action if it already exists",
                        action="store_true")
    self.api('commands.add')('add',
                             self.cmd_add,
                             parser=parser)

    parser = argp.ArgumentParser(add_help=False,
                                 description='list actions')
    parser.add_argument('match',
                        help='list only actions that have this argument in them',
                        default='',
                        nargs='?')
    self.api('commands.add')('list',
                             self.cmd_list,
                             parser=parser)

    parser = argp.ArgumentParser(add_help=False,
                                 description='remove an action')
    parser.add_argument('action',
                        help='the action to remove',
                        default='',
                        nargs='?')
    self.api('commands.add')('remove',
                             self.cmd_remove,
                             parser=parser)

    parser = argp.ArgumentParser(add_help=False,
                                 description='toggle enabled flag')
    parser.add_argument('action',
                        help='the action to toggle',
                        default='',
                        nargs='?')
    action = parser.add_mutually_exclusive_group()
    action.add_argument('-t', '--toggle', action='store_const',
                        dest='togact', const='toggle',
                        default='toggle', help='toggle the action')
    action.add_argument('-d', '--disable', action='store_const',
                        dest='togact', const='disable',
                        help='disable the action')
    action.add_argument('-e', '--enable', action='store_const',
                        dest='togact', const='enable',
                        help='enable the action')
    self.api('commands.add')('toggle',
                             self.cmd_toggle,
                             parser=parser)


    parser = argp.ArgumentParser(add_help=False,
                                 description='get detail for an action')
    parser.add_argument('action',
                        help='the action to get details for',
                        default='',
                        nargs='?')
    self.api('commands.add')('detail',
                             self.cmd_detail,
                             parser=parser)

    parser = argp.ArgumentParser(add_help=False,
                                 description='toggle all actions in a group')
    parser.add_argument('group',
                        help='the group to toggle',
                        default='',
                        nargs='?')
    action = parser.add_mutually_exclusive_group()
    action.add_argument('-t', '--toggle', action='store_const',
                        dest='togact', const='toggle',
                        default='toggle', help='toggle the action')
    action.add_argument('-d', '--disable', action='store_const',
                        dest='togact', const='disable',
                        help='disable the action')
    action.add_argument('-e', '--enable', action='store_const',
                        dest='togact', const='enable',
                        help='enable the action')
    self.api('commands.add')('groupt',
                             self.cmd_grouptoggle,
                             parser=parser)

    for action in self.actions.values():
      self.register_action(action)

    self.api('events.register')('plugin_%s_savestate' % self.sname, self._savestate)

  def register_action(self, action):
    """
    register an action as a trigger
    """
    if 'triggername' not in action:
      action['triggername'] = "action_%s" % action['num']
    self.api('triggers.add')(action['triggername'],
                             action['regex'])
    self.api('events.register')('trigger_%s' % action['triggername'],
                                self.action_matched)

  def unregister_action(self, action):
    """
    unregister an action
    """
    self.api('events.unregister')('trigger_%s' % action['triggername'],
                                  self.action_matched)
    self.api('triggers.remove')(action['triggername'])

  def action_matched(self, args):
    """
    do something when an action is matched
    """
    actionnum = int(args['triggername'].split('_')[-1])
    action = self.lookup_action(actionnum)
    if action:
      akey = action['regex']
      if akey not in self.sessionhits:
        self.sessionhits[akey] = 0
      self.sessionhits[akey] = self.sessionhits[akey] + 1
      action['hits'] = action['hits'] + 1
      self.api('send.msg')('matched line: %s to action %s' % (args['line'],
                                                              akey))
      templ = Template(action['action'])
      newaction = templ.safe_substitute(args)
      sendtype = 'send.' + action['send']
      self.api('send.msg')('sent %s to %s' % (newaction, sendtype))
      self.api(sendtype)(newaction)
    else:
      self.api('send.error')("Bug: could not find action for trigger %s" % \
                              args['triggername'])

  def lookup_action(self, action):
    """
    lookup an action by number or name
    """
    nitem = None
    try:
      num = int(action)
      nitem = None
      for titem in self.actions.keys():
        if num == self.actions[titem]['num']:
          nitem = self.actions[titem]
          break

    except ValueError:
      if action in self.actions:
        nitem = action

    return nitem

  def cmd_add(self, args):
    """
    add user defined actions
    """
    if not args['regex']:
      return False, ['Please include a regex']
    if not args['action']:
      return False, ['Please include an action']

    if not args['overwrite'] and args['regex'] in self.actions:
      return True, ['Action: %s already exists.' % args['regex']]
    else:
      num = 0

      if args['regex'] in self.actions:
        num = self.actions[args['regex']]['num']
      else:
        num = self.api('setting.gets')('nextnum')
        self.api('setting.change')('nextnum', num + 1)

      self.actions[args['regex']] = {
          'num':num,
          'hits':0,
          'regex': args['regex'],
          'action':args['action'],
          'send':args['send'],
          'matchcolor':args['color'],
          'enabled':not args['disable'],
          'group':args['group'],
          'triggername':"action_%s" % num
      }
      self.actions.sync()

      self.register_action(self.actions[args['regex']])

      return True, ['added action %s - regex: %s' % (num, args['regex'])]

    return False, ['You should never see this']

  def cmd_remove(self, args):
    """
    @G%(name)s@w - @B%(cmdname)s@w
      Remove an action
      @CUsage@w: rem @Y<originalstring>@w
        @Yoriginalstring@w    = The original string
    """
    tmsg = []
    if args['action']:
      retval = self.removeaction(args['action'])
      if retval:
        tmsg.append("@GRemoving action@w : '%s'" % (retval))
      else:
        tmsg.append("@GCould not remove action@w : '%s'" % (args['action']))

      return True, tmsg
    else:
      return False, ['@RPlease include an action to remove@w']

  def cmd_list(self, args):
    """
    @G%(name)s@w - @B%(cmdname)s@w
      List actiones
      @CUsage@w: list
    """
    tmsg = self.listactions(args['match'])
    return True, tmsg

  def cmd_toggle(self, args):
    """
    toggle the enabled flag
    """
    tmsg = []

    if args['togact'] == 'disable':
      state = False
    elif args['togact'] == 'enable':
      state = True
    else:
      state = "toggle"
    if args['action']:
      action = self.toggleaction(args['action'], flag=state)
      if action:
        if action['enabled']:
          tmsg.append("@GEnabled action@w : '%s'" % (action['num']))
        else:
          tmsg.append("@GDisabled action@w : '%s'" % (action['num']))
      else:
        tmsg.append("@GDoes not exist@w : '%s'" % (args['action']))
      return True, tmsg

    else:
      return False, ['@RPlease include an action to toggle@w']

  def cmd_grouptoggle(self, args):
    """
    toggle all actions in a group
    """
    tmsg = []
    togglea = []
    if args['togact'] == 'disable':
      state = False
    elif args['togact'] == 'enable':
      state = True
    else:
      state = "toggle"
    if args['group']:
      for i in self.actions:
        if self.actions[i]['group'] == args['group']:
          self.toggleaction(self.actions[i]['num'], flag=state)
          togglea.append('%s' % self.actions[i]['num'])

      if togglea:
        tmsg.append('The following actions were %s: %s' % \
              ('enabled' if state else 'disabled',
               ','.join(togglea)))
      else:
        tmsg.append('No actions were modified')

      return True, tmsg
    else:
      return False, ['@RPlease include a group to toggle@w']

  def cmd_detail(self, args):
    """
    @G%(name)s@w - @B%(cmdname)s@w
      get details of an action
      @CUsage@w: detail 1
        @Yaction@w    = the action to get details, either the number or regex
    """
    tmsg = []
    if args['action']:
      action = self.lookup_action(args['action'])
      if action:
        if 'hits' not in action:
          action['hits'] = 0
        if action['regex'] not in self.sessionhits:
          self.sessionhits[action['regex']] = 0
        tmsg.append('%-12s : %d' % ('Num', action['num']))
        tmsg.append('%-12s : %s' % \
            ('Enabled', 'Y' if action['enabled'] else 'N'))
        tmsg.append('%-12s : %d' % ('Total Hits',
                                    action['hits']))
        tmsg.append('%-12s : %d' % ('Session Hits',
                                    self.sessionhits[action['regex']]))
        tmsg.append('%-12s : %s' % ('Regex', action['regex']))
        tmsg.append('%-12s : %s' % ('Action', action['action']))
        tmsg.append('%-12s : %s' % ('Group', action['group']))
        tmsg.append('%-12s : %s' % ('Match Color',
                                    action['matchcolor']))
        tmsg.append('%-12s : %s' % ('Trigger Name',
                                    action['triggername']))
      else:
        return True, ['@RAction does not exist@w : \'%s\'' % (args['action'])]

      return True, tmsg
    else:
      return False, ['@RPlease include all arguments@w']

  def listactions(self, match):
    """
    return a table of strings that list actions
    """
    tmsg = []
    for action in sorted(self.actions.keys()):
      item = self.actions[action]
      if not match or match in item:
        regex = self.api('colors.stripansi')(item['regex'])
        if len(regex) > 30:
          regex = regex[:27] + '...'
        action = self.api('colors.stripansi')(item['action'])
        if len(action) > 30:
          action = action[:27] + '...'
        tmsg.append("%4s %2s  %-10s %-32s : %s@w" % \
                     (item['num'],
                      'Y' if item['enabled'] else 'N',
                      item['group'],
                      regex,
                      action))
    if not tmsg:
      tmsg = ['None']
    else:
      tmsg.insert(0, "%4s %2s  %-10s %-32s : %s@w" % ('#', 'E', 'Group',
                                                      'Regex', 'Action'))
      tmsg.insert(1, '@B' + '-' * 60 + '@w')

    return tmsg

  def removeaction(self, item):
    """
    internally remove a action
    """
    action = self.lookup_action(item)

    if action and action['regex'] in self.actions:
      self.unregister_action(action)
      del self.actions[action['regex']]
      self.actions.sync()

    return action

  def toggleaction(self, item, flag="toggle"):
    """
    toggle an action
    """
    action = self.lookup_action(item)
    if action:
      if flag == "toggle":
        action['enabled'] = not action['enabled']
      else:
        action['enabled'] = bool(flag)
      if action['enabled']:
        self.register_action(action)
      else:
        self.unregister_action(action)

    return action

  def clearactions(self):
    """
    clear all actiones
    """
    for action in self.actions.values():
      self.unregister_action(action)

    self.actions.clear()
    self.actions.sync()

  def reset(self):
    """
    reset the plugin
    """
    BasePlugin.reset(self)
    self.clearactions()

  def _savestate(self, _=None):
    """
    save states
    """
    self.actions.sync()
Example #3
0
class Plugin(AardwolfBasePlugin):
    """
  a plugin manage info about spells and skills
  """
    def __init__(self, *args, **kwargs):
        """
    initialize the instance
    """
        AardwolfBasePlugin.__init__(self, *args, **kwargs)
        self.saveskillfile = os.path.join(self.savedir, 'skills.txt')
        self.skills = PersistentDict(self.saveskillfile, 'c')
        self.skillsnamelookup = {}
        for i in self.skills:
            self.skillsnamelookup[self.skills[i]['name']] = i

        self.saverecovfile = os.path.join(self.savedir, 'recoveries.txt')
        self.recoveries = PersistentDict(self.saverecovfile, 'c')
        self.recoveriesnamelookup = {}
        for i in self.recoveries:
            self.recoveriesnamelookup[self.recoveries[i]['name']] = i

        self.current = ''
        self.isuptodatef = False

        self.cmdqueue = None

        self.api.get('dependency.add')('cmdq')

        self.api.get('api.add')('gets', self.api_getskill)
        self.api.get('api.add')('isspellup', self.api_isspellup)
        self.api.get('api.add')('getspellups', self.api_getspellups)
        self.api.get('api.add')('sendcmd', self.api_sendcmd)
        self.api.get('api.add')('isaffected', self.api_isaffected)
        self.api.get('api.add')('isblockedbyrecovery',
                                self.api_isblockedbyrecovery)
        self.api.get('api.add')('ispracticed', self.api_ispracticed)
        self.api.get('api.add')('canuse', self.api_canuse)
        self.api.get('api.add')('isuptodate', self.api_isuptodate)
        self.api.get('api.add')('isbad', self.api_isbad)

    def load(self):
        """
    load the plugins
    """
        AardwolfBasePlugin.load(self)

        self.api.get('send.msg')('running load function of skills')

        parser = argparse.ArgumentParser(
            add_help=False, description='refresh skills and spells')
        self.api.get('commands.add')('refresh',
                                     self.cmd_refresh,
                                     parser=parser)

        parser = argparse.ArgumentParser(
            add_help=False, description='lookup skill or spell by name or sn')
        parser.add_argument('skill',
                            help='the skill to lookup',
                            default='',
                            nargs='?')
        self.api.get('commands.add')('lu', self.cmd_lu, parser=parser)

        self.api.get('triggers.add')('spellh_noprompt',
                                     "^\{spellheaders noprompt\}$",
                                     group='slist',
                                     enabled=False,
                                     omit=True)
        self.api.get('triggers.add')('spellh_spellup_noprompt',
                                     "^\{spellheaders spellup noprompt\}$",
                                     group='slist',
                                     enabled=False,
                                     omit=True)
        self.api.get('triggers.add')('spellh_affected_noprompt',
                                     "^\{spellheaders affected noprompt\}$",
                                     group='slist',
                                     enabled=False,
                                     omit=True)
        self.api.get('triggers.add')('spellh_spellline',
                "^(?P<sn>\d+),(?P<name>.+),(?P<target>\d+)," \
                  "(?P<duration>\d+),(?P<pct>\d+),(?P<rcvy>-?\d+),(?P<type>\d+)$",
                group='spellhead', enabled=False, omit=True)
        self.api.get('triggers.add')('spellh_end_noprompt',
                                     "^\{/spellheaders\}$",
                                     group='spellhead',
                                     enabled=False,
                                     omit=True)
        self.api.get('triggers.add')('affoff', "^\{affoff\}(?P<sn>\d+)$")
        self.api.get('triggers.add')(
            'affon', "^\{affon\}(?P<sn>\d+),(?P<duration>\d+)$")
        self.api.get('triggers.add')('recov_noprompt',
                                     "^\{recoveries noprompt\}$",
                                     group='slist',
                                     enabled=False,
                                     omit=True)
        self.api.get('triggers.add')('recov_affected_noprompt',
                                     "^\{recoveries affected noprompt\}$",
                                     group='slist',
                                     enabled=False,
                                     omit=True)
        self.api.get('triggers.add')(
            'spellh_recovline',
            "^(?P<sn>\d+),(?P<name>.+),(?P<duration>\d+)$",
            group='recoveries',
            enabled=False,
            omit=True)
        self.api.get('triggers.add')('recov_end_noprompt',
                                     "^\{/recoveries\}$",
                                     group='recoveries',
                                     enabled=False,
                                     omit=True)
        self.api.get('triggers.add')('recoff', "^\{recoff\}(?P<sn>\d+)$")
        self.api.get('triggers.add')(
            'recon', "^\{recon\}(?P<sn>\d+),(?P<duration>\d+)$")
        self.api.get('triggers.add')(
            'skillgain', "^\{skillgain\}(?P<sn>\d+),(?P<percent>\d+)$")
        self.api.get('triggers.add')('skillfail',
                "^\{sfail\}(?P<sn>\d+),(?P<target>\d+)," \
                  "(?P<reason>\d+),(?P<recovery>-?\d+)$")

        self.api.get('events.register')('trigger_spellh_noprompt',
                                        self.skillstart)
        self.api.get('events.register')('trigger_spellh_spellup_noprompt',
                                        self.skillstart)
        self.api.get('events.register')('trigger_spellh_affected_noprompt',
                                        self.skillstart)
        self.api.get('events.register')('trigger_spellh_spellline',
                                        self.skillline)
        self.api.get('events.register')('trigger_spellh_end_noprompt',
                                        self.skillend)
        self.api.get('events.register')('trigger_affoff', self.affoff)
        self.api.get('events.register')('trigger_affon', self.affon)
        self.api.get('events.register')('trigger_recov_noprompt',
                                        self.recovstart)
        self.api.get('events.register')('trigger_recov_affected_noprompt',
                                        self.recovstart)
        self.api.get('events.register')('trigger_spellh_recovline',
                                        self.recovline)
        self.api.get('events.register')('trigger_recov_end_noprompt',
                                        self.recovend)
        self.api.get('events.register')('trigger_recoff', self.recoff)
        self.api.get('events.register')('trigger_recon', self.recon)

        self.api.get('events.register')('trigger_skillgain', self.skillgain)
        self.api.get('events.register')('trigger_skillfail', self.skillfail)

        self.api.get('events.register')('GMCP:char.status', self.checkskills)

        self.api.get('events.register')('aard_level_tier', self.cmd_refresh)
        self.api.get('events.register')('aard_level_remort', self.cmd_refresh)

        self.cmdqueue = self.api.get('cmdq.baseclass')()(self)
        self.cmdqueue.addcmdtype('slist',
                                 'slist',
                                 "^slist\s*(.*)$",
                                 beforef=self.slistbefore,
                                 afterf=self.slistafter)

        self.checkskills()

    def slistbefore(self):
        """
    stuff to do before doing slist command
    """
        self.api.get('triggers.togglegroup')('slist', True)

    def slistafter(self):
        """
    stuff to do after doing slist command
    """
        self.savestate()
        self.api.get('triggers.togglegroup')('slist', False)

    def afterfirstactive(self, _=None):
        """
    do something on connect
    """
        AardwolfBasePlugin.afterfirstactive(self)
        self.checkskills()

    # check if the spells/skills list is up to date
    def api_isuptodate(self):
        """
    return True if we have seen affected or all spells refresh
    """
        return self.isuptodatef

    def cmd_lu(self, args):
        """
    cmd to lookup a spell
    """
        msg = []
        skill = self.api.get('skills.gets')(args['skill'])
        if skill:
            msg.append('%-8s : %s' % ('SN', skill['sn']))
            msg.append('%-8s : %s' % ('Name', skill['name']))
            msg.append('%-8s : %s' % ('Percent', skill['percent']))
            if skill['duration'] > 0:
                msg.append(
                    '%-8s : %s' %
                    ('Duration', self.api.get('utils.timedeltatostring')(
                        time.time(), skill['duration'])))
            msg.append('%-8s : %s' % ('Target', skill['target']))
            msg.append('%-8s : %s' % ('Spellup', skill['spellup']))
            msg.append('%-8s : %s' % ('Type', skill['type']))
            if skill['recovery']:
                recov = skill['recovery']
                if recov['duration'] > 0:
                    duration = self.api.get('utils.timedeltatostring')(
                        time.time(), recov['duration'])
                    msg.append('%-8s : %s (%s)' %
                               ('Recovery', recov['name'], duration))
                else:
                    msg.append('%-8s : %s' % ('Recovery', recov['name']))
        else:
            msg.append('Could not find: %s' % args['skill'])

        return True, msg

    def cmd_refresh(self, args):
        """
    refresh spells and skills
    """
        self.skills.clear()
        self.recoveries.clear()
        self.cmdqueue.addtoqueue('slist', 'noprompt')
        self.cmdqueue.addtoqueue('slist', 'spellup noprompt')
        msg = ['Refreshing spells and skills']
        return True, msg

    def checkskills(self, _=None):
        """
    check to see if we have spells
    """
        state = self.api.get('GMCP.getv')('char.status.state')
        if state == 3:
            self.api.get('send.msg')('refreshing skills')
            self.api.get('events.unregister')('GMCP:char.status',
                                              self.checkskills)
            self.api.get('A102.toggle')('SPELLUPTAGS', True)
            self.api.get('A102.toggle')('SKILLGAINTAGS', True)
            self.api.get('A102.toggle')('QUIETTAGS', False)
            if len(self.skills) == 0:
                self.cmd_refresh({})
            else:
                self.resetskills()
                self.cmdqueue.addtoqueue('slist', 'affected noprompt')

    def resetskills(self):
        """
    reset the skills
    """
        for i in self.skills:
            self.skills[i]['duration'] = 0
        for i in self.recoveries:
            self.recoveries[i]['duration'] = 0

    def skillgain(self, args):
        """
    handle a skillgain tag
    """
        spellnum = int(args['sn'])
        pct = int(args['percent'])
        if spellnum in self.skills:
            self.skills[spellnum]['percent'] = pct
            self.api.get('events.eraise')('aard_skill_gain', {
                'sn': spellnum,
                'percent': pct
            })

    def skillfail(self, args):
        """
    raise an event when we fail a skill/spell
    """
        spellnum = int(args['sn'])
        reason = FAILREASON[int(args['reason'])]
        ndict = {
            'sn': spellnum,
            'reason': reason,
            'target': FAILTARG[int(args['target'])],
            'recovery': int(args['recovery'])
        }
        if reason == 'dontknow' and self.skills[spellnum]['percent'] > 0:
            self.api.get('send.msg')(
                'refreshing spells because of an unlearned spell')
            self.cmd_refresh({})
        self.api.get('send.msg')('raising skillfail: %s' % ndict)
        self.api.get('events.eraise')('skill_fail_%s' % args['sn'], ndict)
        self.api.get('events.eraise')('skill_fail', ndict)

    def affoff(self, args):
        """
    set the affect to off for spell that wears off
    """
        spellnum = int(args['sn'])
        if spellnum in self.skills:
            self.skills[spellnum]['duration'] = 0
            self.savestate()
            self.api.get('events.eraise')('aard_skill_affoff_%s' % spellnum, {
                'sn': spellnum
            })
            self.api.get('events.eraise')('aard_skill_affoff', {
                'sn': spellnum
            })

    def affon(self, args):
        """
    set the spell's duration when we see an affon
    """
        spellnum = int(args['sn'])
        duration = int(args['duration'])
        if spellnum in self.skills:
            self.skills[spellnum]['duration'] = time.mktime(time.localtime()) + \
                                                              duration
            self.savestate()
            self.api.get('events.eraise')(
                'aard_skill_affon_%s' % spellnum, {
                    'sn': spellnum,
                    'duration': self.skills[spellnum]['duration']
                })
            self.api.get('events.eraise')(
                'aard_skill_affon', {
                    'sn': spellnum,
                    'duration': self.skills[spellnum]['duration']
                })

    def recovstart(self, args):
        """
    show that the trigger fired
    """
        if 'triggername' in args \
            and args['triggername'] == 'trigger_recov_affected_noprompt':
            self.current = 'affected'
        else:
            self.current = ''
        self.api.get('triggers.togglegroup')('recoveries', True)

    def recovline(self, args):
        """
    parse a recovery line
    """
        spellnum = int(args['sn'])
        name = args['name']
        if int(args['duration']) != 0:
            duration = time.mktime(time.localtime()) + int(args['duration'])
        else:
            duration = 0

        if not (spellnum in self.recoveries):
            self.recoveries[spellnum] = {}

        self.recoveries[spellnum]['name'] = name
        self.recoveries[spellnum]['duration'] = duration
        self.recoveries[spellnum]['sn'] = spellnum

        self.recoveriesnamelookup[name] = spellnum

    def recovend(self, args):
        """
    reset current when seeing a spellheaders ending
    """
        self.api.get('triggers.togglegroup')('recoveries', False)
        if self.current == '' or self.current == 'affected':
            self.isuptodatef = True
            self.api.get('send.msg')('sending skills_affected_update')
            self.api.get('events.eraise')('skills_affected_update', {})
        self.cmdqueue.cmddone('slist')

    def recoff(self, args):
        """
    set the affect to off for spell that wears off
    """
        spellnum = int(args['sn'])
        if spellnum in self.recoveries:
            self.recoveries[spellnum]['duration'] = 0
            self.savestate()
            self.api.get('events.eraise')('aard_skill_recoff', {
                'sn': spellnum
            })

    def recon(self, args):
        """
    set the spell's duration when we see an affon
    """
        spellnum = int(args['sn'])
        duration = int(args['duration'])
        if spellnum in self.recoveries:
            self.recoveries[spellnum]['duration'] = \
                              time.mktime(time.localtime()) + duration
            self.savestate()
            self.api.get('events.eraise')(
                'aard_skill_recon', {
                    'sn': spellnum,
                    'duration': self.recoveries[spellnum]['duration']
                })

    def skillstart(self, args):
        """
    show that the trigger fired
    """
        if 'triggername' in args \
            and args['triggername'] == 'spellh_spellup_noprompt':
            self.current = 'spellup'
        elif 'triggername' in args \
            and args['triggername'] == 'spellh_affected_noprompt':
            self.current = 'affected'
        else:
            self.current = ''
        self.api.get('triggers.togglegroup')('spellhead', True)

    def skillline(self, args):
        """
    parse spell lines
    """
        spellnum = int(args['sn'])
        name = args['name']
        target = int(args['target'])
        if int(args['duration']) != 0:
            duration = time.mktime(time.localtime()) + int(args['duration'])
        else:
            duration = 0
        percent = int(args['pct'])
        recovery = int(args['rcvy'])
        stype = int(args['type'])

        if not (spellnum in self.skills):
            self.skills[spellnum] = {}

        self.skills[spellnum]['name'] = name
        self.skills[spellnum]['target'] = TARGET[target]
        self.skills[spellnum]['duration'] = duration
        self.skills[spellnum]['percent'] = percent
        self.skills[spellnum]['recovery'] = recovery
        self.skills[spellnum]['type'] = STYPE[stype]
        self.skills[spellnum]['sn'] = spellnum
        if not ('spellup' in self.skills[spellnum]):
            self.skills[spellnum]['spellup'] = False
        if self.current == 'spellup':
            self.skills[spellnum]['spellup'] = True

        self.skillsnamelookup[name] = spellnum

    def skillend(self, args):
        """
    reset current when seeing a spellheaders ending
    """
        self.api.get('triggers.togglegroup')('spellhead', False)
        self.savestate()
        if self.current:
            evname = 'aard_skill_ref_%s' % self.current
        else:
            evname = 'aard_skill_ref'
        self.api.get('events.eraise')(evname, {})
        self.current = ''

    # get a spell/skill by number
    def api_getskill(self, tsn):
        """
    get a skill
    """
        #self.api.get('send.msg')('looking for %s' % tsn)
        spellnum = -1
        name = tsn
        try:
            spellnum = int(tsn)
        except ValueError:
            pass

        tskill = None
        if spellnum >= 1:
            #self.api.get('send.msg')('%s >= 0' % spellnum)
            if spellnum in self.skills:
                #self.api.get('send.msg')('found spellnum')
                tskill = copy.deepcopy(self.skills[spellnum])
                #tskill = self.skills[spellnum]
            else:
                self.api.get('send.msg')('did not find skill for %s' %
                                         spellnum)

        if not tskill and name:
            #self.api.get('send.msg')('trying name')
            tlist = self.api.get('utils.checklistformatch')(
                name, self.skillsnamelookup.keys())
            if len(tlist) == 1:
                tskill = copy.deepcopy(
                    self.skills[self.skillsnamelookup[tlist[0]]])

        if tskill:
            if tskill['recovery'] and tskill['recovery'] != -1:
                tskill['recovery'] = copy.deepcopy(
                    self.recoveries[tskill['recovery']])
            else:
                tskill['recovery'] = None

        return tskill

    # send the command to active a skill/spell
    def api_sendcmd(self, spellnum):
        """
    send the command to activate a skill/spell
    """
        skill = self.api.get('skills.gets')(spellnum)
        if skill:
            if skill['type'] == 'spell':
                self.api.get('send.msg')('casting %s' % skill['name'])
                self.api.get('send.execute')('cast %s' % skill['sn'])
            else:
                name = skill['name'].split()[0]
                self.api.get('send.msg')('sending skill %s' % skill['name'])
                self.api.get('send.execute')(name)

    # check if a skill/spell can be used
    def api_canuse(self, spellnum):
        """
    return True if the spell can be used
    """
        if self.api.get('skills.isaffected')(spellnum) \
            or self.api.get('skills.isblockedbyrecovery')(spellnum) \
            or not self.api.get('skills.ispracticed')(spellnum):
            return False

        return True

    # check if a skill/spell is a spellup
    def api_isspellup(self, spellnum):
        """
    return True for a spellup, else return False
    """
        spellnum = int(spellnum)
        if spellnum in self.skills:
            return self.skills[spellnum]['spellup']

        return False

    # check if a skill/spell is bad
    def api_isbad(self, spellnum):
        """
    return True for a bad spell, False for a good spell
    """
        skill = self.api.get('skill.gets')(spellnum)
        if (skill['target'] == 'attack' or skill['target'] == 'special') and \
              not skill['spellup']:
            return True

        return False

    # check if a skill/spell is active
    def api_isaffected(self, spellnum):
        """
    return True for a spellup, else return False
    """
        skill = self.api.get('skills.gets')(spellnum)
        if skill:
            return skill['duration'] > 0

        return False

    # check if a skill/spell is blocked by a recovery
    def api_isblockedbyrecovery(self, spellnum):
        """
    check to see if a spell/skill is blocked by a recovery
    """
        skill = self.api.get('skills.gets')(spellnum)
        if skill:
            if 'recovery' in skill and skill['recovery'] and \
                skill['recovery']['duration'] > 0:
                return True

        return False

    # check if a skill/spell is practiced
    def api_ispracticed(self, spellnum):
        """
    is the spell learned
    """
        skill = self.api.get('skills.gets')(spellnum)
        if skill:
            if skill['percent'] > 10:
                return True

        return False

    # get the list of spellup spells/skills
    def api_getspellups(self):
        """
    return a list of spellup spells
    """
        sus = [x for x in self.skills.values() if x['spellup']]
        return sus

    def savestate(self):
        """
    save states
    """
        AardwolfBasePlugin.savestate(self)
        self.skills.sync()
        self.recoveries.sync()