You are not logged in.

Applications: [GameMaster: OPEN] | [Volunteer Testers: OPEN]


This forum will be permanently shut down on Friday 13.07.2018
Please copy or save all important information from old forum before they will be deactivated
We have moved to new board. https://forum.runesofmagic.gameforge.com/Come join us.

1

Thursday, January 21st 2010, 3:25pm

Do-it-yourself Combat Engine

BACKGROUND:

Several months ago, I set out to build a combat engine that I could customize for each class that I played. My first few attempts at this resulted in very lengthy functions that varied greatly from one class to another and maintaining all of them became a chore. So finally I gave up on that approach and came up with a better way. The idea I came up with is to have one function (per class) that defines a table of skills with whatever criteria I wanted for executing each one, and then passes that to another function that finds the first skill in the table that's usable and executes it. Doing it this way streamlined the process and made it easy to maintain.

Note that this does not automate your combat (sorry bot lovers), it just picks the best attack to execute every time you hit the macro. You'll have to hit the macro each time you want your character to perform an action.

INSTALLATION:

If you have any trouble setting this up, please read the link in my sig on creating custom functions. I posted a set of instructions (see post #21 of this thread) that might help as well. If there are errors in your code, you'll get an icon around your minimap that is flashing red. Click on that to get the exact error message.

First off, you'll need to create a folder to extract the .zip files (with their path). If you already have other addons, then the folder you need is already there, just navigate to the path.

Here's the path you should create/navigate to:

Runes of Magic\Interface\Addons

Next open the zip file from the bottom of this post. If you are using WinZip, extract the files into that folder (be sure "Use folder names" is checked). If you are using the standard Windows explorer to view the .zip file, just drag the DIYCE folder to that destination.

To verify you have it setup correctly, you will wind up with 2 files (DIYCE.toc and DIYCE.lua) in this folder:

Runes of Magic\Interface\Addons\DIYCE

Here's the code in the DIYCE.lua file for reference sake:

Source code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
-- DIY Combat Engine version 1.4

g_skill = {}

function Msg(outstr,a1,a2,a3)
    DEFAULT_CHAT_FRAME:AddMessage(tostring(outstr),a1,a2,a3)
end

function ReadSkills()
    g_skill = {}
    local skillname,slot

    Msg("- Reading Class Skills")
    for page = 1,4 do
        slot = 1
        skillname = GetSkillDetail(page,slot)
        repeat
            local a1,a2,a3,a4,a5,a6,a7,a8,skillusable = GetSkillDetail(page,slot)
            if skillusable then
                g_skill[skillname] = { ['page'] = page, ['slot'] = slot }
            end
            slot = slot + 1
            skillname = GetSkillDetail(page,slot)
        until skillname == nil
    end
end
ReadSkills() -- Read skills into g_skill table at login

function PctH(tgt)
    return (UnitHealth(tgt)/UnitMaxHealth(tgt))
end

function PctM(tgt)
    return (UnitMana(tgt)/UnitMaxMana(tgt))
end

function PctS(tgt)
    return (UnitSkill(tgt)/UnitMaxSkill(tgt))
end

function CancelBuff(buffname)
    local i = 1
    local buff = UnitBuff("player",i)

    while buff ~= nil do
        if buff == buffname then
            CancelPlayerBuff(i)
            return true
        end

        i = i + 1
        buff = UnitBuff("player",i)
    end
    return false
end

function BuffTimeLeft(tgt, buffname)
    local cnt = 1
    local buffcmd, bufftimecmd, buff

    if UnitCanAttack("player", tgt) then
        buffcmd = UnitDebuff
        bufftimecmd = UnitDebuffLeftTime
    else
        buffcmd = UnitBuff
        bufftimecmd = UnitBuffLeftTime
    end

    buff = buffcmd(tgt, cnt)

    while buff ~= nil do
        if string.find(buff, buffname) then
            return bufftimecmd(tgt, cnt)
        end
        cnt = cnt + 1
        buff = buffcmd(tgt, cnt)
    end

    return 0
end

function ChkBuff(tgt,buffname)
    local cnt = 1
    local buffcmd = UnitBuff

    if UnitCanAttack("player",tgt) then
        buffcmd = UnitDebuff
    end
    local buff = buffcmd(tgt,cnt)

    while buff ~= nil do
        if string.gsub(buff, "(%()(.)(%))", "%2") == buffname then
            return true
        end
        cnt = cnt + 1
        buff = buffcmd(tgt,cnt)
    end
    return false
end

function BuffList(tgt)
    local cnt = 1
    local buffcmd = UnitBuff
    local buffstr = "/"

    if UnitCanAttack("player",tgt) then
        buffcmd = UnitDebuff
    end
    local buff = buffcmd(tgt,cnt)

    while buff ~= nil do
        buffstr = buffstr..buff.."/"
        cnt = cnt + 1
        buff = buffcmd(tgt,cnt)
    end

    return string.gsub(buffstr, "(%()(.)(%))", "%2")
end

function CD(skillname)
    local firstskill = GetSkillDetail(2,1)
    if (g_skill[firstskill] == nil) or (g_skill[firstskill].page ~= 2) then
        ReadSkills()
    end

    if g_skill[skillname] ~= nil then
        local tt,cd = GetSkillCooldown(g_skill[skillname].page,g_skill[skillname].slot)
        return cd==0
    elseif skillname == nil then
        return false
    else
        Msg("Skill not available: "..skillname)
        return false
    end
end

function MyCombat(Skill, arg1)
    local spell_name = UnitCastingTime("player")
    local talktome = ((arg1 == "v1") or (arg1 == "v2"))
    local action,actioncd,actiondef,actioncnt
    
    if spell_name ~= nil then
        if (arg1 == "v2") then Msg("- ['..spell_name..']", 0, 1, 1) end
        return true
    end

    for x,tbl in ipairs(Skill) do
        if Skill[x].use then
            if string.find(Skill[x].name, "Action:") then
                action = tonumber((string.gsub(Skill[x].name, "(Action:)( *)(%d+)(.*)", "%3")))
                _1,actioncd = GetActionCooldown(action)
                actiondef,_1,actioncnt = GetActionInfo(action)
                if GetActionUsable(action) and (actioncd == 0) and (actiondef ~= nil) and (actioncnt > 0) then
                    if talktome then Msg("- "..Skill[x].name) end
                    UseAction(action)
                    return true
                end
            elseif string.find(Skill[x].name, "Custom:") then
                action = string.gsub(Skill[x].name, "(Custom:)( *)(.*)", "%3")
                if CustomAction(action) then
                    return true
                end
            elseif string.find(Skill[x].name, "Item:") then
                action = string.gsub(Skill[x].name, "(Item:)( *)(.*)", "%3")
                if talktome then Msg("- "..Skill[x].name) end
                UseItemByName(action)
                return true
            elseif CD(Skill[x].name) then
                if talktome then Msg("- "..Skill[x].name) end
                CastSpellByName(Skill[x].name)
                return true
            end
        end
    end
    if (arg1 == "v2") then Msg("- [IDLE]", 0, 1, 1) end

    return false
end
Note that the ChkBuff() function (above) I've posted in the past so if you're using it in another LUA file, please replace it with this version. It does the same operation but is slightly better code wise. Please be aware that both ChkBuff() and BuffList() remove any parentheses from buff/debuff names since those are reserved characters and can cause problems when trying to match them. So don't include any parentheses in your text searches using those functions. Right now this just impacts the Holy Seal debuff... so if you're looking for 3 Holy Seals on the target, use this: ChkBuff("target","Holy Seals 3") or string.find(buffstring,"Holy Seals 3").

I'm also reusing my Msg() function, so make sure you're not using that elsewhere as well. Same with the PctH() function. Previously I called it PctHP so use one or the other (not both) and make sure you adjust your code that calls it accordingly.


HOW IT WORKS:


For those that want to know, this is how the Engine operates: The ReadSkills() function gets run whenever you enter the game or switch classes. It goes through your skill book and memorizes all of your skill numbering and stores it in a global table (g_skill) to be referenced by the CD() function. The CD() function checks the cooldown on whatever skill is passed to it and will return a value of true if the named skill is
not on cooldown and false otherwise, so think of this as a "is the skill ready" check. The MyCombat() function goes through a table (called "Skill") that you define with a list of each action/skill you want to perform looking for the first one that's ready to be used and meets the criteria you specify for that skill.

BUILDING YOUR CUSTOM FUNCTION:


Ok so at this point you have the prerequisite code for the combat engine. Now all that's left to do is build a custom function for your particular class. To keep things simple, you can add your code to the bottom of the DIYCE.lua file, just be aware that file will get overwritten if you reinstall or you download any updates. A better approach is to setup your own .lua file (call it myfunctions.lua) in the same folder and put your custom functions there. Then edit the DIYCE.toc file and include the name of that file. That way when I update the DIYCE code, you can just overwrite it and not have to worry about trying to keep it from wiping out your custom functions. Just remember to add your filename to the bottom of the DIYCE.toc file each time you download a new version of the combat engine.


You can have as many of these custom functions as you want, as long as you give them all unique names. What your custom function will do is define a table of skills and their criteria. Here's an example:


Source code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
[FONT=Verdana]
[/FONT][FONT=Verdana]function ScoutRogue(arg1)
   local Skill = {}
   local i = 0
   local focus = UnitMana("player")
   local enemy = UnitCanAttack("player","target")

   i=i+1; Skill[i] = { name = "Frost Arrow",    use = (not ChkBuff("player","Frost Arrow")) }
   i=i+1; Skill[i] = { name = "Combo Shot",     use = enemy }
   i=i+1; Skill[i] = { name = "Shot",           use = enemy }
   i=i+1; Skill[i] = { name = "Piercing Arrow", use = enemy }
   i=i+1; Skill[i] = { name = "Weak Spot",      use = (enemy and (focus >= 30)) }
-- i=i+1; Skill[i] = { name = "Vampire Arrows", use = (enemy and (focus >= 20)) }
   i=i+1; Skill[i] = { name = "Sapping Arrow",  use = enemy }
   i=i+1; Skill[i] = { name = "Wind Arrows",    use = (enemy and (focus >= 15)) }
   i=i+1; Skill[i] = { name = "Snipe",          use = enemy }

   MyCombat(Skill,arg1)
end
[/FONT]
As you can see, for Frost Arrow (buff) I'm checking to make sure I don't have that buff already. For Combo Shot, Shot, Piercing Arrow, Sapping Arrow, and Snipe I'm just checking to make sure my target isn't friendly, and for Weak Spot, Vampire Arrows, and Wind Arrows I'm also doing a focus check.

To visualize what this is doing, the Skill table might look like this (with the cooldown check listed as well) in the middle of combat when you hit the macro:


Source code

1
2
3
4
5
6
7
8
9
10
11
 [FONT=Verdana][U]
skill          [/U][/FONT][FONT=Verdana]  [U]use   [/U]   [U](CD)    [/U]
Frost Arrow      false    true
Combo Shot       true     false
Shot             true     false
Piercing Arrow   true     true
Weak Spot        false    false
Sapping Arrow    true     true
Wind Arrows      true     true
Snipe            true     true
[/FONT]
The MyCombat() function will go through this table until it finds the first skill with a true condition for "use" that isn't on cooldown. So in this case, Piercing Arrow will get executed. This does not cycle through the list executing the skills in order, instead it goes through the list every time you run it and finds the first skill that's ready. So you're building a skill priority list, not a sequence.

Notice that I have Vampire Arrows defined in the function but I've commented it out ("--" at the beginning of the line). So that way if I want to add it back into my rotation later, I just uncomment the line. If you want to rearrange the priority of the skills, you just cut+paste the entire line wherever you want it in the order.

YOUR IN-GAME MACRO:

Now that you have that setup, you just call the class function from a macro:

/run ScoutRogue()

I have feedback controls built in if you'd like to see what the combat engine is doing. There are 2 levels of feedback, "v1" and "v2". If you pass that to the function it turns on that level of feedback. So if you want full feedback, you'd run this from your macro:

/run ScoutRogue("v2")

I'll post more examples of class functions later.

I highly recommend downloading and using Notepad++ when editing code. It helps to catch things like unbalanced parentheses and other typos. It also displays the line numbers next to each line so if you get an error in game, you can refer to the line number it lists.


Edit: See
post #162 for an explanation of how to use Items (like food) and hotbar Actions (like potions) in your custom function.

Edit #2: Modified to be curse.com friendly (renamed MyFunctions to DIYCE and included the final folder in the .zip).

Edit #3: Code has been uploaded as a Library Project to curse. Here's the URL: http://rom.curse.com/downloads/rom-addons/details/diyce.aspx

Edit #4: See
post #750 for an explanation of how to create and use your own custom execution function to perform more complex actions.

Edit #5: See post #1146 for an example of how to use German characters in your skill names.
Sixpax has attached the following file:
  • DIYCE.zip (1.61 kB - 2,611 times downloaded - latest: Apr 14th 2018, 3:11pm)

2

Thursday, January 21st 2010, 4:45pm

interesting work, i do believe ill have to play around with this a bit

Quoted from "Icarii"

Thread closed.
Long story short, it was banned lol.

Quoted from "Me"

When I understand the validity of your argument, I will dignify it with a response.



K-Dub Spamable Macro's

Priest Macros

Artimis Server:

Sirenian 55K/51W
Draknir 42P/34S
Seronis 52P/49M - Retired

3

Thursday, January 21st 2010, 5:50pm

RogueScout function

Here's another example, this time for Rogue/Scout:

Source code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function RogueScout(arg1, arg2)
    local Skill = {}
    local i = 0
    local energy = UnitMana("player")
    local focus = UnitSkill("player")
    local friendly = (not UnitCanAttack("player", "target"))
    local combat = GetPlayerCombatState()
    local tspell,ttime,telapsed = UnitCastingTime("target")
    local pbuffs = BuffList("player")
    local tbuffs = BuffList("target")

    i=i+1; Skill[i] = { name = "Throat Attack",  use = ((not friendly) and (tspell ~= nil) and (ttime >= 1) and ((ttime - telapsed) > 0.5) and (focus >= 15)) }
    i=i+1; Skill[i] = { name = "Combat Master",  use = ((not string.find(pbuffs, "Combat Master")) and (not string.find(pbuffs, "Informer"))) }
    i=i+1; Skill[i] = { name = "Sneak Attack",   use = ((not friendly) and (energy >= 30) and (arg2 == "behind") and (not combat)) }
    i=i+1; Skill[i] = { name = "Blind Spot",     use = ((not friendly) and (energy >= 25) and (not string.find(tbuffs, "Bleed")) and (arg2 == "behind")) }
    i=i+1; Skill[i] = { name = "Shadowstab",     use = ((not friendly) and (energy >= 35) and (not string.find(tbuffs, "Bleed"))) }
    i=i+1; Skill[i] = { name = "Wound Attack",   use = ((not friendly) and (energy >= 35) and string.find(tbuffs, "Bleed") and string.find(tbuffs, "Grievous Wound")) }
    i=i+1; Skill[i] = { name = "Low Blow",       use = ((not friendly) and (energy >= 35) and string.find(tbuffs, "Bleed")) }
    i=i+1; Skill[i] = { name = "Vampire Arrows", use = (not friendly) }
    i=i+1; Skill[i] = { name = "Shot",           use = (not friendly) }
    
    MyCombat(Skill,arg1)
end
This should closely mimic the RogueCombo function that's floating around on the forums, so you can use this in it's place (although you'll need to adjust it for whatever your secondary is).

Notice in this function I'm having to check player buffs and target debuffs several times, so in order to optimize it's speed I create a buff list once at the beginning of the function and then use string.find() to locate the buff/debuff I need. I'm also passing a 2nd argument (arg2) to this function to notify it if I'm behind the mob or not, then I like to setup a macro using the Shift key as the trigger for a rear attack (Blind Spot/Sneak Attack).

Here's what the macro would look like:

Source code

1
/run if IsShiftKeyDown() then RogueScout("v1","behind") else RogueScout("v1") end
If you'd prefer 2 macros rather than having to hold the Shift key, just use these:

In front of enemy:

Source code

1
/run RogueScout()
Behind enemy:

Source code

1
/run RogueScout("","behind")

Edit: Updated to include Sneak Attack when not in combat.

Edit #2: Added the code for using Throat Attack if the target is casting a spell that can be interrupted.

Edit #3: Reorganized attack rotation due to Shot mechanic changes.

4

Thursday, January 21st 2010, 6:10pm

PriestKnight function

This one is for Priest/Knight. I haven't thoroughly tested this one so use at your own risk. :)

Source code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
function PriestKnight(arg1)
    local Skill = {}
    local i = 0
    local friendly = (not UnitCanAttack("player", "target"))
    local combat = GetPlayerCombatState()
    local pbuffs = BuffList("player")
    local tbuffs = BuffList("target")
    local health, buffs

    if friendly then
        health = PctH("target")
        buffs = tbuffs
    else
        health = PctH("player")
        buffs = pbuffs
    end

    i=i+1; Skill[i] = { name = "Magic Barrier",         use = ((not combat) and (not string.find(pbuffs, "Magic Barrier"))) }
    i=i+1; Skill[i] = { name = "Blessed Spring Water",  use = ((not combat) and (not string.find(pbuffs, "Blessed Spring Water"))) }
    i=i+1; Skill[i] = { name = "Soul Bond",             use = (not string.find(pbuffs, "Soul Bond")) }
    i=i+1; Skill[i] = { name = "Soul Source",           use = (health <= .25) }
    i=i+1; Skill[i] = { name = "Holy Aura",             use = (PctH("player") <= .40) }
    i=i+1; Skill[i] = { name = "Heal",                  use = (health <= .50) }
    i=i+1; Skill[i] = { name = "Urgent Heal",           use = (health <= .80) }
    i=i+1; Skill[i] = { name = "Regenerate",            use = ((health <= .95) and (not string.find(buffs, "Regenerate"))) }
    i=i+1; Skill[i] = { name = "Grace of Life",         use = (not string.find(pbuffs, "Grace of Life")) }
    i=i+1; Skill[i] = { name = "Amplified Attack",      use = (friendly and (not string.find(tbuffs, "Amplified Attack"))) }
    i=i+1; Skill[i] = { name = "Enhanced Armor",        use = (not string.find(pbuffs, "Enhanced Armor")) }
    i=i+1; Skill[i] = { name = "Bone Chill",            use = ((not friendly) and (not string.find(tbuffs, "Bone Chill"))) }
    i=i+1; Skill[i] = { name = "Rising Tide",           use = (not friendly) }

    MyCombat(Skill,arg1)
end
For the non-selfcast spells, I'm doing a health check on the player if the target is unfriendly and on the target if it is friendly. So you can switch from an attack role and still monitor your own health or a support role and monitor someone else's health. You'll need to enable "self cast" in the game options for it to heal yourself when you have a monster targeted.

This one also reapplies buffs that fall off, but on two of them (Magic Barrier and Blessed Spring Water) I'm only casting when out of combat since those aren't a priority. You can certainly modify it so that none of the buffs get re-cast in combat.

5

Friday, January 22nd 2010, 2:42pm

Baseline Knight function

This one is a baseline Knight function so it won't have any secondary or elite skills. We can use this one to build on for the various different Knight combos.

Source code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
function KnightGeneric(arg1, arg2)
    local Skill = {}
    local i = 0
    local friendly = (not UnitCanAttack("player", "target"))
    local shield = (GetEquipSlotInfo(17) ~= nil)
    local combat = GetPlayerCombatState()

    local pbuffs = BuffList("player")
    local tbuffs = BuffList("target")

    i=i+1; Skill[i] = { name = "Whirlwind Shield",     use = ((not friendly) and shield) }
    i=i+1; Skill[i] = { name = "Enhanced Armor",       use = (not string.find(pbuffs, "Enhanced Armor")) }
    i=i+1; Skill[i] = { name = "Holy Seal",            use = (not string.find(pbuffs, "Holy Seal")) }
    i=i+1; Skill[i] = { name = "Threaten",             use = (string.find(tbuffs, "Holy Seals 3") and (not string.find(pbuffs, "Threaten")) and (arg2 == "threaten")) }
    i=i+1; Skill[i] = { name = "Mana Return",          use = (string.find(tbuffs, "Holy Seals 3")) }
    i=i+1; Skill[i] = { name = "Punishment",           use = ((not friendly) and string.find(tbuffs, "Light Seal III")) }
    i=i+1; Skill[i] = { name = "Holy Strike",          use = (not friendly) }

    MyCombat(Skill,arg1)
end
You'll have to pass it the string "threaten" as the 2nd argument if you want to use the Threaten skill or not. So that way you can have one macro for soloing:

Source code

1
/run KnightGeneric()
and one for grouping:

Source code

1
/run KnightGeneric("","threaten")

6

Monday, January 25th 2010, 4:30am

Good stuff, I'm going to have fun with this.

jsalemi

Trainee

Posts: 133

Location: VA, USA

  • Send private message

7

Monday, February 1st 2010, 2:25pm

I thought I posted these, but they seem to have disappeared. Here're some modifications I made to Sixpax's code for the scout/priest combination.

Source code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function ScoutPriest(arg1)
   local Skill = {}
   local i = 0
   local focus = UnitMana("player")
   local friendly = (not UnitCanAttack("player","target"))

   i=i+1; Skill[i] = { ['name'] = "Frost Arrow",    ['use'] = (not ChkBuff("player","Frost Arrow")) }
   i=i+1; Skill[i] = { ['name'] = "Combo Shot",     ['use'] = (not friendly) }
   i=i+1; Skill[i] = { ['name'] = "Shot",           ['use'] = (not friendly) }
   i=i+1; Skill[i] = { ['name'] = "Vampire Arrows", ['use'] = ((not friendly) and (PctH("player") <= .85) and (focus >= 20)) }
   i=i+1; Skill[i] = { ['name'] = "Wind Arrows",    ['use'] = ((not friendly) and (focus >= 15)) }
--   i=i+1; Skill[i] = { ['name'] = "Piercing Arrow", ['use'] = (not friendly) }
   i=i+1; Skill[i] = { ['name'] = "Snipe",          ['use'] = (not friendly) }

   MyCombat(Skill,arg1)
end
I commented out Piercing Arrow because I've had bad luck with it pulling mobs I didn't want to pull. I prefer to control it manually. :) I added a health check to Vamp Arrows, since it gives a small regen due to the s/p elite.

Source code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
function PriestScout(arg1)
   local Skill = {}
   local i = 0
   local tgt = "player"
   local friendly = false
   local combat = GetPlayerCombatState()

   if not UnitCanAttack("player","target") then
      tgt = "target"
      friendly = true
   end
   local health = PctH(tgt)

   local pbuffs = BuffList("player")
   local tbuffs = BuffList("target")

   i=i+1; Skill[i] = { ['name'] = "Soul Bond",             ['use'] = (not string.find(pbuffs,"Soul Bond")) }
   i=i+1; Skill[i] = { ['name'] = "Embrace of the Water Spirit",        ['use'] = (not string.find(pbuffs,"Embrace of the Water Spirit")) }
   i=i+1; Skill[i] = { ['name'] = "Soul Source",           ['use'] = (health <= .20) }
   i=i+1; Skill[i] = { ['name'] = "Holy Aura",             ['use'] = (PctH("player") <= .40) }
   i=i+1; Skill[i] = { ['name'] = "Heal",                  ['use'] = (health <= .50) }
   i=i+1; Skill[i] = { ['name'] = "Urgent Heal",           ['use'] = (health <= .80) }
   i=i+1; Skill[i] = { ['name'] = "Regenerate",            ['use'] = ((not friendly) and (health <= .90) and (not string.find(pbuffs,"Regenerate"))) }
   i=i+1; Skill[i] = { ['name'] = "Regenerate",            ['use'] = (friendly and (health <= .90) and (not string.find(tbuffs,"Regenerate"))) }
   i=i+1; Skill[i] = { ['name'] = "Grace of Life",         ['use'] = ((not combat) and (not string.find(pbuffs,"Grace of Life"))) }
   i=i+1; Skill[i] = { ['name'] = "Amplified Attack",      ['use'] = ((not combat) and (friendly and (not string.find(tbuffs,"Amplified Attack")))) }
   i=i+1; Skill[i] = { ['name'] = "Bone Chill",            ['use'] = ((not friendly) and (not string.find(tbuffs,"Bone Chill"))) }
   i=i+1; Skill[i] = { ['name'] = "Rising Tide",           ['use'] = (not friendly) }
   i=i+1; Skill[i] = { ['name'] = "Magic Barrier",         ['use'] = ((not combat) and (not string.find(pbuffs,"Magic Barrier"))) }
   i=i+1; Skill[i] = { ['name'] = "Blessed Spring Water",  ['use'] = ((not combat) and (not string.find(pbuffs,"Blessed Spring Water"))) }

   MyCombat(Skill,arg1)
end
No major changes from the original, other than adding the p/s elite magic buff to the mix.

Tigsman

Trainee

Posts: 126

Location: ATL

Occupation: Fixing Things

  • Send private message

8

Monday, February 1st 2010, 8:25pm

for the moment, i worked on my wife's R/S. she was looking for a replacement of the old RogueCombo that didnt do what she wanted.

Her first setup is looking like this:

Source code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function RogueScout(arg1,arg2)
   local Skill = {}
   local i = 0
   local energy = UnitMana("player")
   local friendly = (not UnitCanAttack("player","target"))
   local pbuffs = BuffList("player")
   local tbuffs = BuffList("target")

   i=i+1; Skill[i] = { ['name'] = "Combat Master",  ['use'] = (not string.find(pbuffs,"Combat Master")) }
   i=i+1; Skill[i] = { ['name'] = "Poison",         ['use'] = ((not combat) and (not string.find(pbuffs,"Poison"))) }
   i=i+1; Skill[i] = { ['name'] = "Wound Attack",   ['use'] = ((not friendly) and (energy >=35) and (string.find(tbuffs,"Bleed") and (string.find(tbuffs,"Grievous Wound")))) }
   i=i+1; Skill[i] = { ['name'] = "Low Blow",       ['use'] = ((not friendly) and (energy >=35) and (string.find(tbuffs,"Bleed"))) }
   i=i+1; Skill[i] = { ['name'] = "Shadowstab",     ['use'] = ((not friendly) and (energy >=35)) }
   i=i+1; Skill[i] = { ['name'] = "Vampire Arrows", ['use'] = (not friendly) }
   i=i+1; Skill[i] = { ['name'] = "Shot",           ['use'] = (not friendly) }
   
   MyCombat(Skill,arg1)
end


i left arg2 in there for the moment in case she wants to make changes, but for now a simple /run RogueScout() works for her and what she wants it to do. she took out blind spot as she never uses it and moved shot to the end, as using vamp arrows only occurs when shes out of energy and then shot fires if vamp arrows is on cd. its what she wanted.

still deciding if I want to toy with this for my k/w as what works from KnightCombo stll works for me, but I may try to work it out, but it would be multilayered with several different arg2 values, like injecting disarmament, punishment, perhaps a different way of having threaten and mana return fire off, not sure.. still thinking out the logic of the knightgeneric and what i can do with it.


T

9

Monday, February 1st 2010, 8:35pm

One minor issues to be aware of when using this code:

If you have a skill listed in your function that your class cannot use you'll repeatedly get a message that the skill is not available when it tries to execute that one. This isn't a problem other than the unnecessary spam. You can either just ignore it, or comment out that skill in your function, or if you like you can comment out the line in the CD function: Msg("- Skill not available: "..skillname). The nice part about doing the latter is you can leave skills defined in your function that you haven't acquired yet and you won't get the spam. However, the problem with doing it that way is you might have misspelled the skill name in your function and it wouldn't notify you. So I'm leaving it up to the individual to determine how they want to handle that.

Edit: I updated the CD() function to re-read your class skills if you change classes.

10

Monday, February 1st 2010, 8:51pm

Quoted from "Tigsman;219002"

for the moment, i worked on my wife's R/S. she was looking for a replacement of the old RogueCombo that didnt do what she wanted.


I would suggest that you have it only cast Poison when she's out of combat because if it falls off in combat and she's getting attacked, it will continuously try to recast poison over and over until the mob doesn't interrupt her. Take a look at my PriestKnight function for an example of how to add the combat test to some of your skills.


Quoted from "Tigsman;219002"

still deciding if I want to toy with this for my k/w as what works from KnightCombo stll works for me, but I may try to work it out, but it would be multilayered with several different arg2 values, like injecting disarmament, punishment, perhaps a different way of having threaten and mana return fire off, not sure.. still thinking out the logic of the knightgeneric and what i can do with it.


I'll be happy to help with that if you like. Just let me know what the conditions are for using the different skills and I can try to come up with some definitions for their "use". You can start with the KnightGeneric function I posted and let me know what to add/change/delete.

Tigsman

Trainee

Posts: 126

Location: ATL

Occupation: Fixing Things

  • Send private message

11

Monday, February 1st 2010, 9:11pm

Quoted from "Sixpax;219014"

I would suggest that you have it only cast Poison when she's out of combat because if it falls off in combat and she's getting attacked, it will continuously try to recast poison over and over until the mob doesn't interrupt her. Take a look at my PriestKnight function for an example of how to add the combat test to some of your skills.


yeah, the wife appreciates your help, i see the (not combat) in there, i will amend her code for that. thanx

Quoted from "Sixpax;219014"


I'll be happy to help with that if you like. Just let me know what the conditions are for using the different skills and I can try to come up with some definitions for their "use". You can start with the KnightGeneric function I posted and let me know what to add/change/delete.


well, they would be loosely based in the KnightCombo that I use now. one variant runes the HS spam with threaten or mana return thrown in when 3 seals are avail, but the addon does allow for selecting via gui, Threaten, MR or autoswapping when the seals are avail. I know I dont/won't have that here, but im thinking if i can do the variable or not of swapping, just havent figured out how. i.e. seals avail, threaten, seals used.. spam the rest of set, seals avail, use mana return, seals used.. spam the rest again, seals avail, threaten used this time.. in a rotating fashion. helps bring back some mana and still throw out threaten to boost my aggro. this is good for long fights when no angels blessing is avail from my party.

The other variant i use, is spamming disarm until 4 avail, then follow through on the variant listed above. until disarm is no longer active, then it respams disarm. I use this version for relatively slow boss fights or soloing, as it helps bring down the pdef of the target helping me kill faster.


i havent run an instance in a week or so, so give me some time to think about this more. Wish I could fit potting into this somehow, cause, tbh, most autobuffs, don't do a good job of forcing the pots in when they are necessary, and tend to be severely delayed in providing the needed pots (for me I'm usually short on mana) in a crunch, have to stop using macros and simple force the pot on my toon. just sumtin to keep in mind.


T

Tigsman

Trainee

Posts: 126

Location: ATL

Occupation: Fixing Things

  • Send private message

12

Tuesday, February 2nd 2010, 1:24am

Six, how to get rage value? (I'm a k/w, and use rage for some skills)

local rage = UnitMana("player") like the rogue's energy value? or is rage called with something other than UnitMana?

trying to figure out how to add in Slash for example when say rage is above 25 (rage cost of Slash)

I'm guessing the above set value combined with the usage of 'and (rage >=25)' will do what I want, yes?

keep in mind I use both rage and mana so its sorta confusing as to what calls what info.

T

13

Tuesday, February 2nd 2010, 2:00am

I believe "Mana" is always the primary classes power source, and "Skill" is always the secondaries.

Tigsman

Trainee

Posts: 126

Location: ATL

Occupation: Fixing Things

  • Send private message

14

Tuesday, February 2nd 2010, 2:07am

well mana is primary for K, rage is secondary from my W side, what would be the correct call then? never heard of UnitSkill("player") before.



T

jsalemi

Trainee

Posts: 133

Location: VA, USA

  • Send private message

15

Tuesday, February 2nd 2010, 2:15am

GnatB is correct; UnitSkill("player") returns the value of your secondary class' mana/rage/energy/focus.

http://www.theromwiki.com/index.php/API:UnitSkill

Tigsman

Trainee

Posts: 126

Location: ATL

Occupation: Fixing Things

  • Send private message

16

Tuesday, February 2nd 2010, 5:50am

Quoted from "jsalemi;219182"

GnatB is correct; UnitSkill("player") returns the value of your secondary class' mana/rage/energy/focus.

http://www.theromwiki.com/index.php/API:UnitSkill

thank you.

I havent tested my code for functionality yet, but this is what I'll give a go tomorrow.

Source code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
function KWCombat(arg1,arg2)
   local Skill = {}
   local i = 0
   local tgt = "player"
   local rage = UnitSkill("player")
   local friendly = (not UnitCanAttack("player","target"))
   local shield = true
   local health = PctH("player")
   local tgtcast = UnitCastingTime("target")

   if GetEquipSlotInfo(17) == nil then
      shield = false
   end

   local pbuffs = BuffList("player")
   local tbuffs = BuffList("target")

   i=i+1; Skill[i] = { ['name'] = "Holy Shield",          ['use'] = (health <= .20) }
   i=i+1; Skill[i] = { ['name'] = "Resolution",           ['use'] = (health <= .33) }
--   i=i+1; Skill[i] = { ['name'] = "Shield of Atonement",  ['use'] = ((not friendly) and (health <= .40) and shield) }
--   i=i+1; Skill[i] = { ['name'] = "Shield of Valor",      ['use'] = ((not friendly) and (health <= .50) and shield) }
--   i=i+1; Skill[i] = { ['name'] = "Shield of Discipline", ['use'] = ((not friendly) and (tgtcast ~= nil) and shield) }
   i=i+1; Skill[i] = { ['name'] = "Enhanced Armor",       ['use'] = (not string.find(pbuffs,"Enhanced Armor")) }
   i=i+1; Skill[i] = { ['name'] = "Holy Seal",            ['use'] = (not string.find(pbuffs,"Holy Seal")) }
   i=i+1; Skill[i] = { ['name'] = "Threaten",             ['use'] = (string.find(tbuffs,"Holy Seal III") and (not string.find(pbuffs,"Threaten")) and (arg2 == "threaten")) }
   i=i+1; Skill[i] = { ['name'] = "Mana Return",          ['use'] = (string.find(tbuffs,"Holy Seal III")) }
   i=i+1; Skill[i] = { ['name'] = "Whirlwind Shield",     ['use'] = ((not friendly) and shield) }
   i=i+1; Skill[i] = { ['name'] = "Punishment",           ['use'] = (string.find(tbuffs,"Light Seal III")) }
   i=i+1; Skill[i] = { ['name'] = "Disarmament",          ['use'] = (not string.find(tbuffs,"Disarmament IV") and (arg2 == "disarm")) }
   i=i+1; Skill[i] = { ['name'] = "Slash",                ['use'] = ((not friendly) and (rage >=75)) }         
   i=i+1; Skill[i] = { ['name'] = "Holy Strike",          ['use'] = (not friendly) }

   MyCombat(Skill,arg1)
end



T

17

Tuesday, February 2nd 2010, 11:51am

Quoted from "Tigsman;219025"

well, they would be loosely based in the KnightCombo that I use now. one variant runes the HS spam with threaten or mana return thrown in when 3 seals are avail, but the addon does allow for selecting via gui, Threaten, MR or autoswapping when the seals are avail. I know I dont/won't have that here, but im thinking if i can do the variable or not of swapping, just havent figured out how. i.e. seals avail, threaten, seals used.. spam the rest of set, seals avail, use mana return, seals used.. spam the rest again, seals avail, threaten used this time.. in a rotating fashion. helps bring back some mana and still throw out threaten to boost my aggro. this is good for long fights when no angels blessing is avail from my party.


I'm not an expert in LUA and I haven't tested it, but perhaps something like this.

Source code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
-- somewhere up top, perhaps in ReadSkill()
cycleindex=0
cycletable={"Threaten","Mana Return"}


function KnightGenericAutoThreaten(arg1,arg2)
   local Skill = {}
   local i = 0
   local tgt = "player"
   local friendly = (not UnitCanAttack("player","target"))
   local shield = true
   local health = PctH("player")
   local tgtcast = UnitCastingTime("target")

   if GetEquipSlotInfo(17) == nil then
      shield = false
   end

   local pbuffs = BuffList("player")
   local tbuffs = BuffList("target")

   i=i+1; Skill[i] = { ['name'] = "Holy Shield",          ['use'] = (health <= .25) }
   i=i+1; Skill[i] = { ['name'] = "Resolution",           ['use'] = (health <= .33) }
   i=i+1; Skill[i] = { ['name'] = "Shield of Atonement",  ['use'] = ((not friendly) and (health <= .40) and shield) }
   i=i+1; Skill[i] = { ['name'] = "Shield of Valor",      ['use'] = ((not friendly) and (health <= .50) and shield) }
   i=i+1; Skill[i] = { ['name'] = "Shield of Discipline", ['use'] = ((not friendly) and (tgtcast ~= nil) and shield) }
   i=i+1; Skill[i] = { ['name'] = "Enhanced Armor",       ['use'] = (not string.find(pbuffs,"Enhanced Armor")) }
   i=i+1; Skill[i] = { ['name'] = "Holy Seal",            ['use'] = (not string.find(pbuffs,"Holy Seal")) }
   i=i+1; Skill[i] = { ['name'] = "Threaten",             ['use'] = (string.find(tbuffs,"Holy Seal III") and (not string.find(pbuffs,"Threaten")) and (cycletable[cycleindex] <> "Threaten")) }
   i=i+1; Skill[i] = { ['name'] = "Mana Return",          ['use'] = (string.find(tbuffs,"Holy Seal III") and (cycletable[cycleindex] <> "Mana Return")) }
   i=i+1; Skill[i] = { ['name'] = "Whirlwind Shield",     ['use'] = ((not friendly) and shield) }
   i=i+1; Skill[i] = { ['name'] = "Holy Strike",          ['use'] = (not friendly) }



   MyCombat(Skill,arg1)
end

function MyCombat(Skill,arg1)
   local spell_name = UnitCastingTime("player")
   local talktome = false
   
   if (arg1 == "v1") or (arg1 == "v2") then
      talktome = true
   end

   if spell_name ~= nil then
      if (arg1 == "v2") then Msg("- ['..spell_name..']",0,1,1) end
      return false
   end

   for x,tbl in ipairs(Skill) do
      if CD(Skill[x].name) and Skill[x].use then
         if talktome then Msg("- "..Skill[x].name) end
         CastSpellByName(Skill[x].name)
         --Added
         if cycletable ~= nil then
            for z = 0, #cycletable, 1 do
               if Skill[x].name == cycletable[z] then
                  cycleindex = (cycleindex + 1) % (#cycletable + 1)
                  break
               end
            end
         end
         return true
      end
   end
   if (arg1 == "v2") then Msg("- IDLE") end

   return false
end


I added a table, and index to the table, and a small chunk in MyCombat() to manage the index. Not quite sure where to put the initial table and index assignments. I'm guessing in ReadSkill() somewhere or in a prep function. Except we don't want to be editing ReadSkill() for every variation, and having to manually run another funtion every class change is less than ideal. It also shouldn't be hard to add a bit of code to allow arg2 or an arg3 to reset the cycleindex to a specified value.

18

Tuesday, February 2nd 2010, 5:16pm

Quoted from "Tigsman;219025"

well, they would be loosely based in the KnightCombo that I use now. one variant runes the HS spam with threaten or mana return thrown in when 3 seals are avail, but the addon does allow for selecting via gui, Threaten, MR or autoswapping when the seals are avail. I know I dont/won't have that here, but im thinking if i can do the variable or not of swapping, just havent figured out how. i.e. seals avail, threaten, seals used.. spam the rest of set, seals avail, use mana return, seals used.. spam the rest again, seals avail, threaten used this time.. in a rotating fashion. helps bring back some mana and still throw out threaten to boost my aggro. this is good for long fights when no angels blessing is avail from my party.


Personally I would just setup different arg2 values to pass to the function letting it know how you want it to behave. As is, the KnightGeneric() function that I posted will only use Mana Return unless you pass it "threaten" as the arg2 value, in which case it will use Threaten if you don't have that buff and Mana Return otherwise. However, since seals only get applied from white damage, you'll have periods when Threaten is down unless you have a weapon with 2.0 speed (or you're K/R and dual wielding) assuming you don't miss. The slower the weapon, the bigger the gaps, so 2H wielders will see bigger gaps.

So what I suggest is setting values for arg2 like "justthreaten", or "justmanareturn", or "both", etc. Then you could have this piece of code near the top of your class function:

Source code

1
2
3
4
5
6
7
8
9
   local usethreaten,usemanareturn = false,true  -- by default only use Mana Return
   if (arg2 == "justthreaten") then
      usethreaten = true
      usemanareturn = false
   elseif (arg2 == "both") then
      usethreaten = true
   elseif (arg2 ~= nil) then
      Msg("Unknown value for arg2: "..arg2) -- In case of typo
   end
Then down in your Skills definitions, you'd have something like this:

Source code

1
2
   i=i+1; Skill[i] = { name = "Threaten", use = (string.find(tbuffs,"Holy Seals 3") and usethreaten and (not string.find(pbuffs,"Threaten"))) }
   i=i+1; Skill[i] = { name = "Mana Return", use = (string.find(tbuffs,"Holy Seals 3") and usemanareturn) }

Quoted from "Tigsman;219025"

The other variant i use, is spamming disarm until 4 avail, then follow through on the variant listed above. until disarm is no longer active, then it respams disarm. I use this version for relatively slow boss fights or soloing, as it helps bring down the pdef of the target helping me kill faster.


There's several ways you could do this. For soloing, you could setup a variable to see if you're in a party:

Source code

1
2
3
4
   local solo = true
   if GetNumPartyMembers() > 1 then
      solo = false
   end
Then put in a key check, like Shift key:

Source code

1
2
3
4
   local usedisarm = false
   if IsShiftKeyDown() or solo then
      usedisarm = true
   end
Then in your skills code:

Source code

1
   i=i+1; Skill[i] = { name = "Disarmament", use = (not string.find(tbuffs,"Disarmament IV") and usedisarm) }
I just guessed at the debuff name, so please check that.

So Disarm will get executed if you're solo, or if you hold Shift key down, provided the mob doesn't already have it stacked 4 times.

Quoted from "Tigsman;219025"

i havent run an instance in a week or so, so give me some time to think about this more. Wish I could fit potting into this somehow, cause, tbh, most autobuffs, don't do a good job of forcing the pots in when they are necessary, and tend to be severely delayed in providing the needed pots (for me I'm usually short on mana) in a crunch, have to stop using macros and simple force the pot on my toon. just sumtin to keep in mind.


Edit: The latest combat engine code now supports items and potions. See post #162 for instructions on how to incorporate them into your custom function.

19

Thursday, February 4th 2010, 2:38pm

i cant understand , what must i do with this codes ?
create new .xml and paste in it ?

Source code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function ScoutRogue(arg1)
   local Skill = {}
   local i = 0
   local focus = UnitMana("player")
   local friendly = (not UnitCanAttack("player","target"))

   i=i+1; Skill[i] = { ['name'] = "Frost Arrow",    ['use'] = (not ChkBuff("player","Frost Arrow")) }
   i=i+1; Skill[i] = { ['name'] = "Combo Shot",     ['use'] = (not friendly) }
   i=i+1; Skill[i] = { ['name'] = "Shot",           ['use'] = (not friendly) }
   i=i+1; Skill[i] = { ['name'] = "Piercing Arrow", ['use'] = (not friendly) }
   i=i+1; Skill[i] = { ['name'] = "Weak Spot",      ['use'] = ((not friendly) and (focus >= 30)) }
-- i=i+1; Skill[i] = { ['name'] = "Vampire Arrows", ['use'] = ((not friendly) and (focus >= 20)) }
   i=i+1; Skill[i] = { ['name'] = "Sapping Arrow",  ['use'] = (not friendly) }
   i=i+1; Skill[i] = { ['name'] = "Wind Arrows",    ['use'] = ((not friendly) and (focus >= 15)) }
   i=i+1; Skill[i] = { ['name'] = "Snipe",          ['use'] = (not friendly) }

   MyCombat(Skill,arg1)
end

jsalemi

Trainee

Posts: 133

Location: VA, USA

  • Send private message

20

Thursday, February 4th 2010, 3:14pm

No, a .lua -- read the link in Sixpax's signature "Creating your own functions for macros".
You need more than that code, too -- you need the combat engine itself from the every first message in this thread.