Skip to content

How to update the code

Romain Mondon-Cancel edited this page Jul 20, 2018 · 2 revisions

Introduction

In this page, you will find useful information about how a contributor can find where to update the code for his specific class, depending on what he wants to do.

I want to update the spells of my class

Classes files are in the hrgenerator/classes folder. Let's say I want to update the spells in the paladin.py file, to add Wake of Ashes to the Retribution Paladin.

In each file, there is a Python dict called SPELL_INFO containing the data for all the spells registered for the class. This dict has the following structure:

SPELL_INFO {
    <class_name>: {
        COMMON: {<spells common to all the specs>},
        <spec_1>: {<spells for spec_1>},
        <spec_2>: {<spells for spec_2>},
    },
}

Therefore, I just have to find the RETRIBUTION key and add the Wake of Ashes spell to it. A spell entry is formatted as follows:

RETRIBUTION: {
   '<spell_name>':        {SPELL: <spell_id>,
                           <additional facultative properties>},
}

Here, spell_name should be exactly the name as it appears in simc APLs, and spell_id should simply be the ID of the spell in-game. A spell can have multiple attributes:

  • SPELL [int]: the ID of the castable spell.
  • BUFF [int]: the ID of the buff the spell applies on the player. Some passives may have a BUFF line without a SPELL line.
  • DEBUFF [int]: the ID of the debuff the spell applies on the target. Some passives may have a DEBUFF line without a SPELL line.
  • RANGE [int]: the range, in yards, of the spell. Default is melee range, if it is not specified.
  • USABLE [bool]: calls IsUsableP instead of IsCastableP in lua, in case this specific spell has to run additional checks on top of the default.
  • INTERRUPT [bool]: if the spell is an interrupt; this adds additional conditions, to check whether the player wants interrupts to be displayed or not.
  • CD [bool]: if the spell should be considered as a cooldown; this checks if the player wants cooldowns to be predicted by the addon.
  • OGCDAOGCD [bool]: flags the spell as an OffGCD as OffGCD, which will be displayed as a suggested spell instead of the main spell.
  • GCDAOGCD [bool]: flags the spell as a GCD as OffGCD, which will be displayed as a suggested spell instead of the main spell.
  • MELEE [bool]: a very specific tag, used in Demon Hunter APL to check whether the unit is in melee range by taking his dash into consideration.

It is of course possible to add custom tags, if they are used by the class otherwise.

I want to define a custom lua function to include in my class module

Custom lua functions are defined in the hrgenerator/luafunctions folder. Each file contains a single function, which is written in lua.

To add a new function, for example the function IsInMeleeRange to the Demon Hunter file, I first create the file IsInMeleeRange.lua in hrgenerator/luafunctions, containing the code for the function. Then, in the demonhunter.py file in the hrgenerator/classes file, there is a Python dict called CLASS_FUNCTIONS in which I can define the custom lua functions to import when using a specific spec. CLASS_FUNCTIONS has the following structure:

CLASS_FUNCTIONS = {
    <class_name>: {
        COMMON: [<common function names>],
        <spec_1>: [<function names for spec_1>],
        <spec_2>: [<function names for spec_2>],
    },
}

The lists of function names should contain the string names of the files in hrgenerator/luafunctions containing the functions to add, without the .lua extension. Therefore, in my case, I will add 'IsInMeleeRange' to the HAVOC key.

I want to customize the behavior of the generator for my class

Although possible, this part requires more work. To be able to properly customize the behavior of the generator, I first have to identify where in the parser the behavior should be updated. In most cases, what we want is call a custom function when the parser reaches a specific expression, to run additional checks instead of the default behavior. For example, let's say we want, as a Warlock, to call the custom function ActiveUAs when reading the expression buff.active_uas.stack.

The first thing I have to identify is where in the code this expression is handled. In this case, it's pretty easy. In the hrgenerator/objects/expressions.py file, the class Buff handles everything that starts with buff.. This class has a method stack, which parses expressions of the form buff.{spell}.stack. Therefore, this is this method that we have to updated.

To update it, we need to write a Python decorator, i.e. a second-order function that takes the stack method and returns a modified version of this stack method, with the additional behavior. In our case, our decorator will look as follows:

def affliction_active_uas_stack(fun):
    from ..objects.lua import Method

    def stack(self):
        """
        Return the arguments for the expression buff.active_uas.stack.
        """
        if (self.condition.parent_action.player.spec.simc == AFFLICTION
                and self.condition.condition_list[1] == 'active_uas'):
            self.object_ = None
            self.method = Method('ActiveUAs')
            self.args = []
        else:
            fun(self)

    return stack

Our decorator, affliction_active_uas_stack, takes the original stack method under the fun parameter, and defines a new stack method. In this method, it first tests whether the current spec is AFFLICTION:

  • self is the current Buff expression object,
  • self.condition is the current condition (the buff.{something}.stack part), of class ConditionExpression,
  • self.condition.parent_action is the APL line itself of class Action,
  • self.condition.parent_action.player is a reference to the current player, of class Player,
  • self.condition.parent_action.player.spec is the spec of the current player, of class PlayerSpec,
  • self.condition.parent_action.player.spec.simc is the simc string of the spec, from the line spec=affliction.

It also checks if the second element of the condition is the string active_uas: self.condition.condition_list is the list of strings of the current condition, after splitting on dots. Therefore, if parsing buff.active_uas.stack, self.condition.condition_list will contain ["buff", "active_uas", "stack"].

An expression contains three important attributes: object_, method and args, each of a lua parsable class as defined in the hrgenerator/objects/lua.py or hrgenerator/objects/executions.py files (depending on the context). In any case, the expression will be parsed as

  • <object_>:<method>(<args>) if object_ is not None,
  • <method>(<args>) if object_ is None.

Therefore, as in our case we want to parse buff.active_uas.stack as ActiveUAs(), we want to define self.object_ as None, self.method as Method("ActiveUAs") and self.args as [] (i.e. no argument).

Finally, once the decorator is properly defined, all we have to do is tell the parser to apply it to the stack method of the Buff class: for that, we simply have to add

{
    'class_name': 'Buff',
    'method': 'stack',
    'decorator': affliction_active_uas_stack,
},

in the DECORATORS dict under the WARLOCK key.